You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
With settings.run.mcp enabled (the default), every headless phase agent
receives the user's ENTIRE Claude Desktop config: getMcpServersConfig() (src/lib/system.ts:179) reads claude_desktop_config.json and claude-code.ts:119 passes the whole
object to the SDK query().
Two consequences, both observed in the 2026-08-10 overnight run on
ad-motion:
Phases should get an allowlist, not a passthrough:
Default: inject ONLY the sequant MCP server (and anything the project's
own .mcp.json declares — that file is designed to be secret-free).
Never read claude_desktop_config.json for phase execution. It is a
different trust domain: servers a human uses interactively in the
desktop app, not servers an autonomous agent should hold.
If passthrough is ever wanted, make it opt-in per server: settings.run.mcpAllowlist: ["name", ...].
AC
Phase agent argv contains no MCP servers beyond sequant + project .mcp.json entries (test: spawn a phase with a populated fake
desktop config; assert its --mcp-config excludes those servers)
No secret-bearing env values from the desktop config appear in the
phase process argv
Docs: run.mcp documented as sequant-server injection, not
desktop passthrough
Related: #161 (introduced the passthrough), #934 (containment incident
whose transcript captured the leaked credentials), #395.
Symptom
With
settings.run.mcpenabled (the default), every headless phase agentreceives the user's ENTIRE Claude Desktop config:
getMcpServersConfig()(src/lib/system.ts:179) readsclaude_desktop_config.jsonandclaude-code.ts:119passes the wholeobject to the SDK
query().Two consequences, both observed in the 2026-08-10 overnight run on
ad-motion:
Capability over-grant. Autonomous phase agents inherited an
email-sending MCP server (SMTP send-as), a Stripe server, a Google
Sheets server with a service-account file, a GitHub server with a PAT,
and a filesystem server rooted at
~/Desktop+~/Documents. A phaseagent implementing a GitHub issue needs none of these — and fix(containment): a phase agent killed its own orchestrator — no Bash signal guard, no process-group isolation, machine re-prompts read as human consent #934's
incident shows phase agents will use surprising capabilities when they
misread a situation.
Secrets in argv. The SDK serializes the merged config into
--mcp-config <json>on theclaudechild's command line. ClaudeDesktop configs cannot use
${VAR}references (the desktop app doesnot expand them), so they contain literal secrets — which are then
visible to ANY local process via
ps, and get captured into agenttranscripts whenever an agent runs
ps aux(this is exactly how livecredentials ended up in the fix(containment): a phase agent killed its own orchestrator — no Bash signal guard, no process-group isolation, machine re-prompts read as human consent #934 incident transcript).
Proposed fix
Phases should get an allowlist, not a passthrough:
own
.mcp.jsondeclares — that file is designed to be secret-free).claude_desktop_config.jsonfor phase execution. It is adifferent trust domain: servers a human uses interactively in the
desktop app, not servers an autonomous agent should hold.
settings.run.mcpAllowlist: ["name", ...].AC
.mcp.jsonentries (test: spawn a phase with a populated fakedesktop config; assert its
--mcp-configexcludes those servers)phase process argv
run.mcpdocumented as sequant-server injection, notdesktop passthrough
Related: #161 (introduced the passthrough), #934 (containment incident
whose transcript captured the leaked credentials), #395.