fix(security): set strictMcpConfig to block cloned-PR .mcp.json auto-load - #210
Conversation
…load settingSources:[] (issue #191) gates settings.json/CLAUDE.md but not a project .mcp.json, which the CLI auto-discovers from cwd by default. With cwd = the cloned PR tree under bypassPermissions, a hostile .mcp.json could register a stdio MCP server whose command runs on connect, the same RCE primitive class as #191. strictMcpConfig:true emits --strict-mcp-config, restricting MCP loading to the explicitly-injected mcpServers. Closes #196 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR hardens MCP (Model Context Protocol) server discovery in ChangesMCP Configuration Hardening
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR hardens the Claude Agent SDK invocation against hostile MCP auto-discovery from an attacker-controlled cloned PR working directory by forcing MCP loading to only the explicitly injected mcpServers set.
Changes:
- Set
strictMcpConfig: truein the Agent SDKqueryOptionsto suppress project.mcp.jsonauto-discovery. - Add a regression test asserting
strictMcpConfigis enabled while preservingmcpServersforwarding and the existingsettingSources: []pin.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/core/executor.ts |
Pins strictMcpConfig: true alongside settingSources: [] to prevent cloned-tree .mcp.json auto-load. |
test/core/executor.test.ts |
Adds a regression test ensuring strictMcpConfig is set and mcpServers/settingSources are preserved. |
# [1.14.0](v1.13.0...v1.14.0) (2026-06-30) ### Bug Fixes * **agent-sdk:** pass settingSources [] so SDK ignores cloned PR .claude/settings.json ([#195](#195)) ([153fef3](153fef3)) * **check:** derive scoped-executor scan set from filesystem (dead guard) ([#208](#208)) ([8b0e8ef](8b0e8ef)) * **daemon:** sweep full workspace triple on startup and crash exit ([#239](#239)) ([56fa714](56fa714)) * **idempotency:** gate side-effecting handlers with Valkey claim to prevent redelivery duplicates ([#212](#212)) ([68dacdb](68dacdb)) * **infrastructure:** patch krb5 HIGH CVE-2026-40356 in shared Docker base ([#180](#180)) ([1d1bc3b](1d1bc3b)) * **mcp:** redact Octokit error tool-results and widen GitHub token regex ([#238](#238)) ([675d610](675d610)) * **mcp:** wrap GitHub-touching MCP servers + state-fetchers in retryWithBackoff ([#205](#205)) ([319beb9](319beb9)) * **observability:** canonicalise child-logger entity id under entityNumber ([#178](#178)) ([808ca46](808ca46)) * **security:** gate LLM scanner redacted_body to deletion-only ([#206](#206)) ([d52cf78](d52cf78)) * **security:** set strictMcpConfig to block cloned-PR .mcp.json auto-load ([#210](#210)) ([2c58ec1](2c58ec1)) * **testing:** run colocated src/**/*.test.ts in CI + add drift guard ([#204](#204)) ([5990e0d](5990e0d)) ### Features * **agent-sdk:** block destructive Bash at runtime via PreToolUse hook ([#241](#241)) ([f3132f2](f3132f2)) * **observability:** add 12 structured Pino event families with Zod-strict schemas ([#251](#251)) ([eaad36b](eaad36b)) * **observability:** add queue_wait_ms to dispatcher offer/no-daemon logs ([#207](#207)) ([7a5cfb0](7a5cfb0)) * **observability:** add structured retry.* events ([#225](#225)) ([6713cbf](6713cbf)) * **observability:** emit failed_stage and failed_stage_delta_ms on pipeline.failed ([#244](#244)) ([4f2483c](4f2483c)) * **observability:** emit structured idempotency events on all 4 claimDelivery outcomes ([#242](#242)) ([e1e7f9e](e1e7f9e)) * **observability:** installationId on loggers + config-free MCP retry ([#189](#189)) ([ba09f76](ba09f76)), closes [#177](#177) [#184](#184) * **observability:** log + persist SDK token usage on executions ([#209](#209)) ([5407dcd](5407dcd)) * **observability:** log octokit rate-limit headers via hook.after ([#183](#183)) ([30e1715](30e1715)) * **observability:** periodic fleet-state gauge snapshot ([#186](#186)) ([7429460](7429460)) * **observability:** redact crash logs via uncaughtException/unhandledRejection handlers ([#181](#181)) ([d4248f4](d4248f4)) * **observability:** structured dispatcher + heartbeat log events ([#188](#188)) ([c615bc4](c615bc4)) * **observability:** structured pino logger for stdio MCP servers ([#185](#185)) ([5244e8a](5244e8a)) * **observability:** structured pipeline.stage timing events with delta_ms ([#182](#182)) ([4125971](4125971))
|
🎉 This PR is included in version 1.14.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
What
Closes #196. Follow-up to #191 / PR #195. That PR pinned
settingSources: []to close the.claude/settings.jsonSessionStart-hook RCE in the cloned PR working tree. This closes the sibling vector: a hostile.mcp.jsoncommitted in the same tree.Determination (verified, not assumed)
settingSources: []does not gate project.mcp.jsondiscovery:node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs) mapsstrictMcpConfig→ the CLI--strict-mcp-configflag,mcpServers→--mcp-config, andsettingSources→--setting-sources.--bareflag ("skip auto-discovery of hooks, skills, plugins, MCP servers, auto memory, and CLAUDE.md") shows MCP servers are auto-discovered by default and are a distinct item from settings/memory (whatsettingSourcescontrols).--strict-mcp-configrestricts MCP loading to the--mcp-configset, ignoring all other configuration including project.mcp.json.So with
cwd= the cloned PR tree +permissionMode: bypassPermissions, a hostile.mcp.jsonwas auto-discovered and loaded — registering a stdio MCP server whosecommandruns on connect, the same RCE primitive class as #191.Fix
src/core/executor.ts— addstrictMcpConfig: truetoqueryOptions, next to thesettingSources: []pin, with an inline comment recording the determination (the TS docstring only mentions validation, but the flag it emits also suppresses discovery).test/core/executor.test.ts— regression test:options.strictMcpConfig === true, the injectedmcpServersare still forwarded (no-loss), andsettingSourcesstays[].No-loss: the bot supplies all its MCP servers explicitly via
mcpServers; it never relies on a project.mcp.json, so this keeps every legitimate server and blocks only the attacker-controlled file.Acceptance criteria
.mcp.jsonatcwdIS auto-loaded independent ofsettingSources: [].strictMcpConfig: trueadded with a regression test; injectedmcpServerspreserved.Out of scope (residual, operator-controlled, not cloned-tree)
~/.claude.json, plugin-provided, and MCPB-bundled MCP servers live in operator-controlled locations, not the attacker-controllable PR tree. Both cloned-tree attacker config classes (settings.json,.mcp.json) are now closed.🤖 Generated with Claude Code
Summary by CodeRabbit