fix(ci): support external contributors in Claude workflows - #316
Conversation
Pass the scoped GITHUB_TOKEN explicitly because the Claude OIDC token exchange rejects users without write access. Isolate untrusted issue and pull request input behind pinned write helpers before using pull_request_target.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 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 |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed77eefd62
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| Use the code review skill to run this review: /code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number || inputs.pr_number }} | ||
| claude_args: | | ||
| --add-dir pr-head --allowedTools "Read,Glob,Grep,Bash(.github/scripts/pr-review-comment.sh:*),Bash(gh pr view:*),Bash(gh pr diff:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*)" |
There was a problem hiding this comment.
Remove write-capable Git commands from the allowlist
When untrusted PR content persuades Claude to invoke an approved Git command, git log --format=... --output=.github/scripts/pr-review-comment.sh can replace the helper with attacker-chosen shell code while preserving its executable mode; Git documents --output=<file> as “output to a specific file instead of stdout” in its diff options. Claude can then invoke the replaced helper under the separate allowlist entry and execute arbitrary commands with the write-capable token, completely bypassing the pinned-PR restriction. Expose Git through a wrapper that rejects output/config/execution options, or keep the privileged helper outside the model-writable workspace.
Useful? React with 👍 / 👎.
| type: number | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} |
There was a problem hiding this comment.
Use the pull request identity in the concurrency key
For external PRs, github.head_ref contains only the source branch name and does not include the fork owner or PR number. Two contributors commonly using a branch such as fix therefore share this concurrency group, so opening or synchronizing one PR cancels the other PR's in-progress review and leaves it without the promised comment. Key automatic runs by github.event.pull_request.number (and manual runs by inputs.pr_number) instead.
Useful? React with 👍 / 👎.
Greptile SummaryThis PR enables Claude review and issue-triage workflows for external contributors by moving review execution to
Confidence Score: 3/5The PR is not yet safe to merge because untrusted PR content is processed while a write-scoped checkout credential remains readable by the model. The constrained helpers and isolated PR checkout are sound, but the base checkout's persisted credential creates a concrete source-to-public-comment disclosure path; immutable action pinning is also needed to harden these privileged workflows. Files Needing Attention: .github/workflows/claude-code-review.yml and .github/workflows/claude-issue-triage.yml
|
| Filename | Overview |
|---|---|
| .github/workflows/claude-code-review.yml | Enables external PR review with an isolated head checkout and narrow tools, but leaves the write-scoped base-checkout credential accessible to the model. |
| .github/workflows/claude-issue-triage.yml | Adds minimally scoped external issue triage, with the remaining concern that credential-bearing actions use mutable tags. |
| .github/scripts/pr-review-comment.sh | Safely pins review comments to the workflow-selected repository and PR while rejecting empty bodies. |
| .github/scripts/triage-issue.sh | Restricts issue writes to labels and comments on the workflow-selected issue, with quoted arguments and label validation. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
External[External contributor] --> Event[Issue or pull-request event]
Event --> Workflow[Trusted base-repository workflow]
Workflow --> Base[Trusted base checkout]
Workflow --> Head[Untrusted PR head in pr-head/]
Base --> Credential[Persisted Git credential]
Head --> Claude[Claude action with restricted tools]
Credential --> Claude
Claude --> ReviewHelper[Pinned PR-comment helper]
Claude --> TriageHelper[Pinned issue-triage helper]
ReviewHelper --> PR[Triggering pull request]
TriageHelper --> Issue[Triggering issue]
Reviews (1): Last reviewed commit: "fix(ci): support external contributors" | Re-trigger Greptile
| - name: Checkout base repository | ||
| uses: actions/checkout@v7.0.1 | ||
| with: | ||
| fetch-depth: 1 |
There was a problem hiding this comment.
The trusted base checkout keeps persist-credentials enabled by default. As a result, its write-scoped GITHUB_TOKEN remains available through Git credential configuration while Claude has unrestricted Read access and processes attacker-controlled pull request content. A prompt-injected review could read the credential and pass it to the arbitrary-body comment helper, publicly disclosing a live token capable of modifying pull requests. Disable credential persistence on the base checkout and keep credentials outside model-readable paths.
How this was verified: The base checkout persists the write-scoped token, unrestricted Read reaches the checkout's Git configuration, and the permitted helper posts any nonempty supplied body to the attacker-visible pull request.
| - name: Checkout base repository | |
| uses: actions/checkout@v7.0.1 | |
| with: | |
| fetch-depth: 1 | |
| - name: Checkout base repository | |
| uses: actions/checkout@v7.0.1 | |
| with: | |
| fetch-depth: 1 | |
| persist-credentials: false |
| with: | ||
| fetch-depth: 1 | ||
|
|
||
| - uses: anthropics/claude-code-action@v1 |
There was a problem hiding this comment.
The new triage workflow runs anthropics/claude-code-action@v1 and actions/checkout@v7.0.1 through mutable tags while supplying an OAuth secret and an issues-write token. The review workflow also uses mutable references while handling external contributions. If an upstream tag is moved or compromised, replacement action code could access these credentials and make unauthorized issue or pull request changes. Pin these actions to full commit SHAs, as the repository's other production workflows do.
How this was verified: The mutable Claude action receives both explicitly supplied credentials, and the repository's other workflows pin the same checkout release to a full commit SHA.
Summary
GITHUB_TOKENexplicitly and opt in viaallowed_non_write_usersValidation
actionlint1.7.12 (both workflows)bash -n(both helper scripts)