Skip to content

fix(ci): keep the Claude app token out of the on-demand reviewer's reach - #473

Merged
theCodeDrift merged 1 commit into
mainfrom
fix/claude-review-job-token
Oct 6, 2026
Merged

theCodeDrift merged 1 commit into
mainfrom
fix/claude-review-job-token

Conversation

@theCodeDrift

Copy link
Copy Markdown
Member

claude-code-review-on-demand.yml and claude-code-focus-on-demand.yml both assumed the reviewer's GitHub reach was bounded by the job's permissions: block and that persist-credentials: false kept tokens out of the tree. Neither held, measured on run 37418782166 and in the action source at the pinned SHA (v1.0.221):

  • The token wasn't the job token. With no github_token input, the action exchanges OIDC for a claude GitHub App token (src/github/token.ts). It requests no specific scopes, so the token carries the installation's: contents, workflows and actions write among them. contents: read limited nothing.
  • It was written back into .git/config. After checkout, the action rewrites the origin URL to embed that token (src/github/operations/git-config.ts; logged as "Updated remote URL with authentication token"), so Read plus gh pr comment had a path from it to a public comment.
  • The allowlist was wider than written. The action merges its own defaults into --allowedTools, including Glob, Grep, git add/commit/rm and its git-push.sh. The run log's "SDK options" shows the real set.

Changes

Same three changes in both workflows:

  • github_token: ${{ github.token }}, so the action skips the OIDC exchange and uses the job token, bounded by contents: read, pull-requests: write, issues: write.
  • Dropped id-token: write, which only served that exchange. Claude authenticates with the OAuth token.
  • --disallowedTools "Read(.git/**),Read(//proc/**)". /proc because the action also passes its token to the Claude process's environment. Claude Code applies Read deny rules to Grep and Glob only best-effort, which is why the token scoping matters on its own.

Comments now come from github-actions[bot] rather than claude[bot]. Nothing keys on the login: both verify steps filter on .user.type == "Bot", and incremental mode matches its marker text.

Verifying

issue_comment workflows run from main, so this can't be exercised before merge. On the first @claude /review after merging, the run log should show "Using provided GITHUB_TOKEN for authentication" rather than "Using GITHUB_TOKEN from OIDC", both deny rules under disallowedTools in "SDK options", and a posted review.

No changeset: workflow-only, nothing ships in the package.

claude-code-action, with no github_token input, swaps the job token for a
`claude` GitHub App token minted over OIDC. Its scopes come from the app
installation (contents, workflows and actions write), not from the job's
`permissions:` block, so `contents: read` was never the boundary. The
action then writes that token into the origin remote URL in .git/config
after checkout, undoing `persist-credentials: false`, and the model's
`Read` plus `gh pr comment` is a path from that file to a public comment.

Hand both on-demand workflows the job's own token, drop `id-token: write`,
and deny `Read(.git/**)` and `Read(//proc/**)`, the latter because the
action also passes its token to the Claude process's environment.
@theCodeDrift
theCodeDrift merged commit f904432 into main Oct 6, 2026
8 checks passed
@theCodeDrift
theCodeDrift deleted the fix/claude-review-job-token branch October 6, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant