ci(claude): add label-gated fork-PR review job#1338
Closed
cliffhall wants to merge 1 commit into
Closed
Conversation
Adds a hardened `pull_request_target` path so @claude can review external fork PRs without exposing secrets to prompt injection. A maintainer must apply the `claude-review` label to trigger the job; the label is auto-removed after each run. Mitigations: no Bash glob / Edit / Write / WebFetch, no fork code is executed, the fork's `.mcp.json` is deleted after checkout and replaced with a trusted base-repo-controlled config that exposes only the modelcontextprotocol.io/mcp docs server. The claude-code-action is pinned by commit SHA to v1.0.99 to avoid the recurring AJV schema-drift crash family. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Member
Author
|
Superseded by a PR from the same branch on the origin repo (avoids the fork-PR path for an internal change). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pull_request_targetpath so@claudecan review external fork PRs without exposing secrets to prompt injection.mcp-docsMCP server athttps://modelcontextprotocol.io/mcp— for protocol lookups during review. NoWebFetch, no other MCP servers, no unrestricted Bash.anthropics/claude-code-actionto v1.0.99 by commit SHA (12310e4417c3473095c957cb311b3cf59a38d659) to avoid the recurring AJV-schema-drift crash family (Does mcp inspector support 06-18 auth flow? #852/Calling a tool with structured output that returns an array of objects throws an error #872/Add implicit default origin in case of port 80 #892/Fix $ref resolution in request schema properties before validation #902/Add proposed spec for UX #947/Missing support for icon theme #965/Updating recommendation for V2 UI Components #980/Add Tasks support #1013, root cause tracked in anthropics/claude-code-action#1021).The first-party
claudejob is unchanged — it already has the collaborator gate and PR-head-SHA checkout from #1270.This mirrors the pattern in the companion PR for the servers repo: modelcontextprotocol/servers#4222.
Threat model
The standard
claude.ymlpattern triggers onissue_comment/pull_request_review_comment. On a fork PR, GitHub uses the workflow from the base repo (good) but reads diff/comment/file content from the fork (untrusted). Risks:pull-requests: write,ANTHROPIC_API_KEY, or other secrets reachable from Claude's tool surface.pull_request_targetis the canonical foot-gun: trusted workflow code runs with base-repo secrets while checking out untrusted code. The pattern below uses it deliberately, with strict guardrails.Mitigations in the new
claude-fork-reviewjobpull_request_target: types: [labeled], filtered to label nameclaude-review. A drive-by contributor cannot apply labels.persist-credentials: false,fetch-depth: 1. No install/build/test step — fork code is never executed.contents: read,pull-requests: write,issues: read. Nothing else.--allowedToolsis justmcp__github_inline_comment__create_inline_comment,mcp__mcp-docs,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr list:*). No unrestrictedBash, noEdit/Write, noWebFetch. An injection that successfully redirects Claude can post a comment and query the docs server — that is it.https://modelcontextprotocol.io/mcp, via a base-repo-controlled MCP config written to$RUNNER_TEMPbefore the action runs. The fork has no input into which servers Claude connects to..mcp.jsonshadowing.mcp.jsonfrom cwd in addition to anything passed via--mcp-config. A fork could ship a.mcp.jsonpointing at attacker-controlled MCP servers. The Prepare trusted MCP config step explicitlyrm -f .mcp.jsonbefore launching Claude, and writes the trusted config under$RUNNER_TEMP(outside the fork checkout, so the fork cannot shadow it). The block comment above that step spells out the threat.--max-turns 8.always()step removes theclaude-reviewlabel after every run, so a fresh maintainer eyeball is required to re-trigger.Action version pinning
Pinned to
anthropics/claude-code-action@12310e4417c3473095c957cb311b3cf59a38d659(v1.0.99). The action validates settings against a schema that ships inside the private Claude Agent SDK, and SDK schema drift on upstream bumps has crashed the runtime before any API call in at least eight versions (exit 1 in ~150–300ms,total_cost_usd: 0, minified stack trace mentioningdepsCount/dependencies). v1.0.99 predates the v1.0.100install.shregression (#1242) and is the most recent known-working anchor. Until anthropics/claude-code-action#1021 lands, SHA pinning is non-negotiable for this action.Residual risks
modelcontextprotocol.io/mcpbecome input to Claude during the review. It is a read-only docs endpoint, so low-risk, but worth naming.Pre-merge checklist
claude-reviewlabel in repo settings.secrets.ANTHROPIC_API_KEYis available topull_request_targetevents (not environment-restricted).claude-reviewlabel, watch the run logs). Abort merge if you see exit code 1 within ~300ms withtotal_cost_usd: 0and a stack trace mentioningdependenciesordepsCount— that's the AJV crash.anthropics/claude-code-actionto Dependabot, but treat bump PRs as review-required — do not auto-merge. Each bump needs a smoke test.Test plan
claude-fork-reviewjob does not fire automatically.claude-reviewlabel; job runs, posts review, removes label.@claudefirst-party flow is unaffected..mcp.jsonshadowing test. Open a draft fork PR that ships a.mcp.jsonpointing at a bogus host (e.g.https://example.invalid/mcp). Apply theclaude-reviewlabel. In the run logs, confirm: (a) the Prepare trusted MCP config step'srm -f .mcp.jsonsucceeded, (b) Claude's MCP-server connection log shows onlymodelcontextprotocol.io, and (c) no connection attempt was made to the fork-supplied host. If Claude connects to the bogus host, the shadowing protection has failed — do not merge.🤖 Generated with Claude Code