Follow-up from #571 (deferred there as explicitly out of scope).
Context
#571 closed the consent gap: project-local <cwd>/.mcp.json no longer auto-spawns arbitrary commands (now opt-in via AFK_ALLOW_PROJECT_MCP). But consent is not containment — once an operator opts in (AFK_ALLOW_PROJECT_MCP=1), a project-local stdio server still spawns with the full ambient privileges of the AFK process: unrestricted filesystem write, network egress, and access to the operator's environment.
Why this matters
A project server's env block can reference ${SECRET} via the live expander (src/agent/mcp/env.ts), landing operator secrets in the child's environment. Even a trusted opt-in server is a large blast radius if it (or a dependency it shells out to) is later compromised — e.g. an npx pkg@latest-style command resolving to swapped code after a registry push (the exact reason #571 rejected a JSON-hash trust store).
Proposed control (separate from consent)
Restrict what a project-local (and ideally any) stdio MCP server can do at spawn time:
- scrub / allowlist the
env handed to the child (don't forward the full process.env; require explicit passthrough);
- optional network-egress and fs-write restrictions where the OS supports it (per-OS cost — likely
sandbox-exec on macOS, seccomp/bubblewrap on Linux, best-effort elsewhere);
- surface the granted capabilities in the startup notice.
Spawn chokepoint: src/agent/mcp/transport.ts (new StdioClientTransport), fed by McpManager.fromConfig (src/agent/mcp/manager.ts).
Acceptance (rough)
Follow-up from #571 (deferred there as explicitly out of scope).
Context
#571 closed the consent gap: project-local
<cwd>/.mcp.jsonno longer auto-spawns arbitrary commands (now opt-in viaAFK_ALLOW_PROJECT_MCP). But consent is not containment — once an operator opts in (AFK_ALLOW_PROJECT_MCP=1), a project-local stdio server still spawns with the full ambient privileges of the AFK process: unrestricted filesystem write, network egress, and access to the operator's environment.Why this matters
A project server's
envblock can reference${SECRET}via the live expander (src/agent/mcp/env.ts), landing operator secrets in the child's environment. Even a trusted opt-in server is a large blast radius if it (or a dependency it shells out to) is later compromised — e.g. annpx pkg@latest-style command resolving to swapped code after a registry push (the exact reason #571 rejected a JSON-hash trust store).Proposed control (separate from consent)
Restrict what a project-local (and ideally any) stdio MCP server can do at spawn time:
envhanded to the child (don't forward the fullprocess.env; require explicit passthrough);sandbox-execon macOS, seccomp/bubblewrap on Linux, best-effort elsewhere);Spawn chokepoint:
src/agent/mcp/transport.ts(new StdioClientTransport), fed byMcpManager.fromConfig(src/agent/mcp/manager.ts).Acceptance (rough)