fix: use Gitea for T4 egress contract
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Waiting to run
sop-checklist / review-refire (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 11s
CI / Python Lint & Test (pull_request) Successful in 10s
E2E API Smoke Test / detect-changes (pull_request) Successful in 9s
CI / Detect changes (pull_request) Successful in 10s
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 11s
E2E Chat / detect-changes (pull_request) Successful in 11s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 8s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 7s
Harness Replays / detect-changes (pull_request) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 4s
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Successful in 31s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 58s
gate-check-v3 / gate-check (pull_request) Successful in 7s
sop-checklist / all-items-acked (pull_request) acked: 7/7
sop-checklist / na-declarations (pull_request) N/A: qa-review, security-review
CI / Canvas (Next.js) (pull_request) Successful in 6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 7s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m39s
E2E Chat / E2E Chat (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 3s
Harness Replays / Harness Replays (pull_request) Successful in 4s
CI / Platform (Go) (pull_request) Successful in 4m49s
CI / all-required (pull_request) Successful in 8m2s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
qa-review / approved (pull_request) Refired via /qa-recheck by unknown
security-review / approved (pull_request) Refired via /security-recheck by unknown
sop-tier-check / tier-check (pull_request) Refired via /refire-tier-check; tier-check failed (see workflow log)
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Waiting to run
sop-checklist / review-refire (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 11s
CI / Python Lint & Test (pull_request) Successful in 10s
E2E API Smoke Test / detect-changes (pull_request) Successful in 9s
CI / Detect changes (pull_request) Successful in 10s
E2E Staging SaaS (full lifecycle) / E2E Staging SaaS (pull_request) Has been skipped
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 11s
E2E Chat / detect-changes (pull_request) Successful in 11s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 8s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 7s
Harness Replays / detect-changes (pull_request) Successful in 7s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 4s
E2E Staging SaaS (full lifecycle) / pr-validate (pull_request) Successful in 31s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 58s
gate-check-v3 / gate-check (pull_request) Successful in 7s
sop-checklist / all-items-acked (pull_request) acked: 7/7
sop-checklist / na-declarations (pull_request) N/A: qa-review, security-review
CI / Canvas (Next.js) (pull_request) Successful in 6s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 7s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 1m39s
E2E Chat / E2E Chat (pull_request) Successful in 3s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 3s
Harness Replays / Harness Replays (pull_request) Successful in 4s
CI / Platform (Go) (pull_request) Successful in 4m49s
CI / all-required (pull_request) Successful in 8m2s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
qa-review / approved (pull_request) Refired via /qa-recheck by unknown
security-review / approved (pull_request) Refired via /security-recheck by unknown
sop-tier-check / tier-check (pull_request) Refired via /refire-tier-check; tier-check failed (see workflow log)
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# 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 into the container so the agent can manage other containers (T4 use case: agent-as-orchestrator). Proven by 'docker version' returning a server section, which requires the daemon to answer over the socket."
|
||||
severity: hard
|
||||
source: "provisioner.go applyHostConfig T4 branch (case 4)"
|
||||
probe: "sudo -n 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: "[ -d /proc/1/root ] && sudo -n nsenter --target 1 --pid -- true"
|
||||
- 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"
|
||||
@@ -121,7 +121,7 @@ func T4PrivilegeContract() []T4Capability {
|
||||
{
|
||||
Name: "docker_socket_reachable",
|
||||
Description: "/var/run/docker.sock is bind-mounted into the container so the agent can manage other containers (T4 use case: agent-as-orchestrator). Proven by 'docker version' returning a server section, which requires the daemon to answer over the socket.",
|
||||
Probe: `sudo -n docker version --format '{{.Server.Version}}' >/dev/null 2>&1`,
|
||||
Probe: `sudo -n docker info >/dev/null 2>&1`,
|
||||
Severity: SeverityHard,
|
||||
Source: "provisioner.go applyHostConfig T4 branch (case 4)",
|
||||
},
|
||||
@@ -145,7 +145,7 @@ func T4PrivilegeContract() []T4Capability {
|
||||
},
|
||||
{
|
||||
Name: "network_egress_https",
|
||||
Description: "Generic HTTPS egress works. T4 is unconstrained network; the canonical test target is the Gitea instance over its public name, which any fork user can also resolve. Any reachable HTTPS endpoint satisfies it — the YAML carries the recommended targets but accepts any 200/301/302.",
|
||||
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.",
|
||||
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; ` +
|
||||
@@ -153,10 +153,9 @@ func T4PrivilegeContract() []T4Capability {
|
||||
Severity: SeverityHard,
|
||||
Source: "task #174 brief",
|
||||
RequiredEgress: []string{
|
||||
// Public, no auth, returns a small JSON.
|
||||
// Molecule-owned, public, no auth, returns a small JSON.
|
||||
// Adopters override via MOLECULE_T4_EGRESS_TARGETS.
|
||||
"https://api.github.com/zen",
|
||||
"https://www.google.com/generate_204",
|
||||
"https://git.moleculesai.app/api/v1/version",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -169,7 +168,7 @@ func T4PrivilegeContract() []T4Capability {
|
||||
{
|
||||
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.",
|
||||
Probe: `[ -d /proc/1/root ] && [ "$(sudo -n readlink /proc/1/ns/pid)" = "$(sudo -n readlink /proc/self/ns/pid)" ]`,
|
||||
Probe: `[ -d /proc/1/root ] && sudo -n nsenter --target 1 --pid -- true`,
|
||||
Severity: SeverityHard,
|
||||
Source: "provisioner.go applyHostConfig T4 branch (case 4): hostCfg.PidMode = 'host'",
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package provisioner
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -77,6 +78,19 @@ func TestT4PrivilegeContract_CoreCapabilitiesPresent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestT4PrivilegeContract_DefaultEgressUsesMoleculeOwnedEndpoint(t *testing.T) {
|
||||
for _, c := range T4PrivilegeContract() {
|
||||
for _, target := range c.RequiredEgress {
|
||||
if strings.Contains(target, "github.com") {
|
||||
t.Errorf("capability %q default egress target must not depend on GitHub mirror/API: %s", c.Name, target)
|
||||
}
|
||||
if strings.Contains(target, "google.com") {
|
||||
t.Errorf("capability %q default egress target must not depend on external Google endpoint: %s", c.Name, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestT4PrivilegeContract_HardCapabilitiesMajority sanity-checks that
|
||||
// the contract is not silently advisory-only. If someone marks
|
||||
// everything as "advisory" the gate becomes a no-op without anyone
|
||||
@@ -142,6 +156,17 @@ func TestAsYAML_EscapesEmbeddedQuotes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratedT4CapabilitiesYAMLMatchesSSOT(t *testing.T) {
|
||||
got, err := os.ReadFile("t4_capabilities.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("read generated t4_capabilities.yaml: %v", err)
|
||||
}
|
||||
want := AsYAML(T4PrivilegeContract())
|
||||
if string(got) != want {
|
||||
t.Fatal("generated t4_capabilities.yaml drifted from T4PrivilegeContract; regenerate with `go run ./cmd/t4-contract-dump > internal/provisioner/t4_capabilities.yaml`")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAgentUIDConsistency ties the contract to the existing
|
||||
// provisioner-side AgentUID const. The probe for "agent_uid_1000"
|
||||
// hard-codes `id -u == 1000`; if AgentUID ever changes (no one
|
||||
|
||||
Reference in New Issue
Block a user