Disable auto-approval of project MCP servers when .mcp.json changes in PR#942
Disable auto-approval of project MCP servers when .mcp.json changes in PR#942ashwin-ant wants to merge 3 commits intomainfrom
Conversation
Code Review SummaryThe security motivation here is sound — auto-approving project MCP servers when Must fix
Should fix
Consider
|
…n PR When a PR modifies .mcp.json, the checked-out working directory contains a version that may differ from the base branch. Blindly setting enableAllProjectMcpServers=true in that context auto-approves those MCP servers without the user having reviewed them. Fix: before calling setupClaudeCodeSettings, check whether .mcp.json changed in the triggering PR using the GitHub REST API (pulls.listFiles). If it did, pass mcpJsonChanged=true to suppress the enableAllProjectMcpServers=true override. When .mcp.json is unmodified (the common case), behaviour is unchanged. - Add mcpJsonChanged parameter to setupClaudeCodeSettings - Add PR file-list check in src/entrypoints/run.ts - Update tests: update existing override test, add two new mcpJsonChanged=true tests
- Use octokit.rest.paginate() to fetch all pages of PR changed files, preventing attackers from padding PRs with 100+ files to push .mcp.json off the first page - Change catch block to fail closed (mcpJsonChanged=true) so MCP servers are not auto-approved when the API call fails
3b21ebf to
f4ac33b
Compare
|
@claude review |
|
Claude finished @km-anthropic's task in 1m 39s —— View job Code Review for PR #942
Good security motivation — preventing auto-approval of MCP servers when Must fix1. Broken call site in
await setupClaudeCodeSettings(
process.env.INPUT_SETTINGS,
undefined, // homeDir
);After the signature change to await setupClaudeCodeSettings(
process.env.INPUT_SETTINGS,
false, // mcpJsonChanged — standalone base-action doesn't detect PR changes
undefined, // homeDir
);Should fix2. User In The } else {
delete settings.enableAllProjectMcpServers;
console.log(
`Removing enableAllProjectMcpServers because .mcp.json changed in this PR`,
);
}3. Missing test for Tests cover the cases where input doesn't set Status of prior review items
|
There was a problem hiding this comment.
The pagination and fail-closed fixes address two of my earlier findings — thanks for that. Two issues remain: the base-action/src/index.ts call site still has a stale // homeDir comment and skips the new mcpJsonChanged parameter (works by accident), and the else branch in setup-claude-code-settings.ts still doesn't delete settings.enableAllProjectMcpServers when mcpJsonChanged is true, so user input with enableAllProjectMcpServers: true can bypass the protection.
Extended reasoning...
Overview
This PR adds a security check to disable auto-approval of project MCP servers when .mcp.json is modified in a PR. It touches three files: setup-claude-code-settings.ts (new mcpJsonChanged parameter), run.ts (detection logic using GitHub API), and the test file.
Progress since last review
Two of my four critical findings from the initial review have been fixed:
- Pagination: Now uses
octokit.rest.paginateinstead of a singlelistFilescall - Fail-closed: Error handling now defaults to
mcpJsonChanged = true
Both fixes match the suggestions I provided.
Remaining issues
-
Call site in
base-action/src/index.ts(not in PR diff): Line 16 still passesundefinedwith the comment// homeDir, but after the signature changeundefinednow maps tomcpJsonChanged. This works by accident (falsy =false= old behavior), andhomeDiruses its default. The comment is incorrect and the behavior is fragile. -
Security gap: When
mcpJsonChangedistrue, theelsebranch only logs a message. It does notdelete settings.enableAllProjectMcpServers. If the user'sINPUT_SETTINGSincludesenableAllProjectMcpServers: true, the merge on line 59 ({ ...settings, ...inputSettings }) will have already set it, and theelsebranch won't remove it — effectively bypassing the protection this PR is meant to provide.
Security risks
This PR is explicitly a security mitigation against RCE via malicious MCP servers in PRs. The remaining security gap (#2 above) means the protection can be circumvented if a workflow happens to set enableAllProjectMcpServers: true in its settings input. While this requires a specific workflow configuration, it undermines the security invariant the PR is trying to establish.
Level of scrutiny
High — this is security-critical code that guards against RCE. The fixes so far are good, but the remaining gap in the else branch should be addressed before merging.
When mcpJsonChanged is true, delete enableAllProjectMcpServers from the merged settings object so user-provided INPUT_SETTINGS cannot re-enable it and bypass the check. Also fix the stale positional arg in the standalone base-action call site and add test coverage for the bypass case.
There was a problem hiding this comment.
All 5 issues from my prior reviews are now resolved (index.ts call site, pagination, fail-closed, settings override, missing test). Deferring to human review since this touches security-sensitive MCP server auto-approval logic.
Extended reasoning...
Overview
This PR adds a security guard: when a PR modifies .mcp.json, the action skips auto-approving project MCP servers to prevent RCE via attacker-injected MCP configurations. It touches 4 files: run.ts (detection logic), setup-claude-code-settings.ts (conditional approval), index.ts (call site update), and the test file.
Status of previously raised issues
All 5 must-fix/should-fix items from my two prior reviews are now resolved:
- Broken
index.tscall site — Now passesfalseexplicitly with correct comment - Pagination bypass — Uses
octokit.rest.paginateto fetch all changed files - Fail-open error handling — Defaults to
mcpJsonChanged=true(fail-closed) - User settings override —
elsebranch now doesdelete settings.enableAllProjectMcpServers - Missing test — Added test for
enableAllProjectMcpServers: true+mcpJsonChanged: true
The two remaining "consider" items (positional boolean parameter, no unit test for run.ts detection logic) are style/coverage suggestions, not correctness issues.
Security risks
The PR is a security improvement. The implementation correctly handles the key attack vectors: large PRs (pagination), API failures (fail-closed), nested .mcp.json paths, and user settings bypass. One residual limitation is GitHub's hard cap of 3,000 files per PR via the API, but this is an API-level constraint.
Level of scrutiny
This modifies security-sensitive logic controlling which MCP servers are auto-trusted. While the implementation is now correct and well-tested, a human should verify the security design decisions (fail-closed behavior, scope of .mcp.json path matching, interaction with other settings).
Other factors
Test coverage is comprehensive for the settings logic. The detection logic in run.ts lacks direct unit tests but is straightforward. A human reviewer (ddworken) confirmed two of the original bugs. The PR description accurately reflects the changes.
What and why
When a PR modifies
.mcp.json, the checked-out working directory contains a version that may differ from the base branch. The current code unconditionally setsenableAllProjectMcpServers = trueinsetupClaudeCodeSettings, which auto-approves all project MCP servers — including those introduced in the PR's.mcp.json— without the user having reviewed them.This PR makes auto-approval conditional: if
.mcp.jsonchanged in the triggering PR, we skip settingenableAllProjectMcpServers = trueso those servers are not automatically trusted.Changes
base-action/src/setup-claude-code-settings.tsAdded a
mcpJsonChangedboolean parameter (defaultfalse). TheenableAllProjectMcpServers = trueassignment now only runs whenmcpJsonChangedis false. When it's true, a log message is emitted instead. All existing behaviour is preserved for non-PR events and for PRs that don't touch.mcp.json.src/entrypoints/run.tsBefore calling
setupClaudeCodeSettings, useoctokit.rest.pulls.listFilesto fetch the files changed in the PR (when the event is a PR-type event). If any changed file is.mcp.jsonor ends with/.mcp.json, passmcpJsonChanged=true. Errors from the API call are caught and logged; on any failure the flag staysfalse(safe default).base-action/test/setup-claude-code-settings.test.tsfalseformcpJsonChanged(no behaviour change)mcpJsonChanged=falsemcpJsonChanged=true: one verifyingenableAllProjectMcpServersstaysundefinedwhen not set in input, one verifying it staysfalsewhen the input sets it tofalse