# T4 privilege contract — generated from
# molecule-ai/molecule-core workspace-server/internal/provisioner/t4_privilege_contract.go
# RFC: molecule-ai/internal#456
# Do NOT edit this file by hand; regenerate via `go run ./cmd/t4-contract-dump > t4_capabilities.yaml`.
version: 1
agent_uid: 1000
capabilities:
  - name: "agent_home_writable"
    description: "/agent-home is writable by the agent (Files API split per task #128). The Files API redesign uses /agent-home as the user-writable root; the agent must be able to create files there without sudo."
    severity: hard
    source: "task #128 Files API redesign; memory reference_post_suspension_pipeline"
    probe: "TF=/agent-home/.t4-cap-write-probe-${MOLECULE_T4_PROBE_ID:-$$}; echo ok > \"$TF\" && [ \"$(cat \"$TF\")\" = \"ok\" ] && rm -f \"$TF\""
  - name: "agent_uid_1000"
    description: "The container's primary process (the runtime, post-gosu) runs as uid 1000, not root. T4 grants full machine access via privileged + host PID + Docker socket — the WORKLOAD inside that privileged container must still be unprivileged to prevent every untrusted code execution from being trivially root-on-host."
    severity: hard
    source: "RFC internal#456 §2.1.2; memory feedback_hermes_listpeers_401_token_root600_unreadable_by_uid1000"
    probe: "[ \"$(id -u)\" = \"1000\" ]"
  - name: "auth_token_agent_owned"
    description: "/configs/.auth_token is owned by uid 1000 (== AgentUID) so the a2a_mcp_server can read its bearer. In SaaS mode molecule-runtime itself writes the token via save_token() — the ownership equals the runtime's exec uid. If the runtime ever runs as root, this fails and list_peers 401s (the Hermes class bug)."
    severity: hard
    source: "RFC internal#456 §10; memory feedback_hermes_listpeers_401_token_root600_unreadable_by_uid1000"
    probe: "[ -e /configs/.auth_token ] && [ \"$(stat -c '%u' /configs/.auth_token)\" = \"1000\" ]"
  - name: "docker_socket_reachable"
    description: "/var/run/docker.sock is bind-mounted and host Docker is reachable from the T4 container. The probe enters the host mount+PID namespaces before running docker info so it validates the same host-control path production agents use, instead of depending on the template image's Docker CLI/socket group details."
    severity: hard
    source: "provisioner.go applyHostConfig T4 branch (case 4)"
    probe: "sudo -n nsenter --target 1 --mount --pid -- docker info >/dev/null 2>&1"
  - name: "host_fs_write_readback"
    description: "Host filesystem is mounted at /host and the agent can write+read+remove a file there via sudo. Proves real host reach (not just a PID-1 namespace trick on an isolated init)."
    severity: hard
    source: "RFC internal#456 §11"
    probe: "MARKER=\"t4cap-$(date +%s)-$RANDOM\"; PROBE_FILE=\"/host/tmp/.t4-cap-probe-${MOLECULE_T4_PROBE_ID:-$$}\"; sudo -n sh -c \"echo $MARKER > $PROBE_FILE\" && [ \"$(sudo -n cat $PROBE_FILE)\" = \"$MARKER\" ] && sudo -n rm -f $PROBE_FILE"
  - name: "host_root_reach_via_nsenter"
    description: "The uid-1000 agent can attain host root via `sudo -n nsenter --target 1 --mount --pid -- id -u` returning 0. This is the T4 escalation leg: full machine access means the agent CAN escalate to host root deliberately, even though it does not run as root by default."
    severity: hard
    source: "RFC internal#456 §11; memory reference_per_template_privilege_contract_class_audit_2026_05_16"
    probe: "[ \"$(sudo -n nsenter --target 1 --mount --pid -- id -u)\" = \"0\" ]"
  - name: "list_peers_http_200"
    description: "The platform list_peers HTTP endpoint (served by the in-container a2a_mcp_server) returns HTTP 200 when called from uid 1000 with the bearer from /configs/.auth_token. This proves the WHOLE token-ownership chain end-to-end: token written under correct uid → reader uid matches → bearer non-empty → platform accepts. A self-contained empirical test for the Hermes class bug."
    severity: hard
    source: "memory reference_openclaw_fresh_provision_nonfunctional_anthropic_default_unroutable; memory reference_openclaw_mcp_peer_wiring_rootcause"
    probe: "BEARER=$(cat /configs/.auth_token 2>/dev/null || echo \"\"); [ -n \"$BEARER\" ] || exit 1; PORT=$(cat /configs/.platform_port 2>/dev/null || echo \"8080\"); STATUS=$(curl -sS -o /dev/null -w '%{http_code}' -H \"Authorization: Bearer $BEARER\" \"http://127.0.0.1:${PORT}/list_peers\"); [ \"$STATUS\" = \"200\" ]"
  - name: "network_egress_https"
    description: "Generic HTTPS egress works. T4 is unconstrained network; the canonical test target is the Molecule-owned Gitea middleman over its public name. CI must not depend on GitHub or other mirrors for this probe. Any reachable HTTPS endpoint satisfies it — the YAML carries the recommended targets but accepts any 200/301/302."
    severity: hard
    source: "task #174 brief"
    probe: "for U in $MOLECULE_T4_EGRESS_TARGETS; do   C=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 8 \"$U\");   case \"$C\" in 2*|3*) exit 0;; esac; done; exit 1"
    required_egress:
      - "https://git.moleculesai.app/api/v1/version"
  - name: "pid_host_visible"
    description: "Host PID namespace is shared (--pid=host). The container can see host process 1 (systemd or pid-1 on the EC2 instance). Required for nsenter into host mount/pid namespaces."
    severity: hard
    source: "provisioner.go applyHostConfig T4 branch (case 4): hostCfg.PidMode = 'host'"
    probe: "[ \"$(sudo -n nsenter --target 1 --mount --pid -- id -u)\" = \"0\" ]"
  - name: "privileged_flag_observable"
    description: "Container is started with --privileged. Observable from inside via /proc/self/status CapEff containing CAP_SYS_ADMIN. Defense-in-depth for the provisioner emission side."
    severity: advisory
    source: "provisioner.go applyHostConfig T4 branch (case 4)"
    probe: "grep -q '^CapEff:.*ffffffffff' /proc/self/status"
