fix(ci): restrict @claude mention workflow to trusted authors - #174
Merged
Conversation
This repo is public with issues enabled. The job only checked that the text contained "@claude", so any GitHub user could trigger it by opening an issue. Unlike a fork's pull_request run, `issues` and `issue_comment` events execute in this repo's context and DO receive repository secrets, so those runs had CLAUDE_CODE_OAUTH_TOKEN in the environment. And because the action executes whatever instructions are in the comment that tagged it, reaching that token needed no injection trick — just a comment. Gates on author_association (OWNER / MEMBER / COLLABORATOR) per event, checked against the account that actually triggered it. CONTRIBUTOR is excluded: it only means someone had a commit merged once. The check is deliberately not one shared OR across event types. On issue_comment the payload carries both comment.author_association and issue.author_association, so a flat OR would have let an untrusted account comment on a trusted author's issue and pass. Also: - timeout-minutes: 15, so a wedged run cannot bill the subscription for the 6h default. - concurrency group per issue/PR, without cancel-in-progress — a superseded @claude task is still work someone asked for. - drops id-token: write, which is only needed for OIDC federation and is dead weight on the static-token path. No evidence of abuse: every run of this workflow to date was a legitimate internal one, the most recent on 2026-06-23. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Claude finished @MaryWylde's task in 1m 14s —— View job Reviewing
|
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.
The problem
claude.ymlfired on any text containing@claude. There was no check on who wrote it — and this is our only public repo, with issues enabled.The part that makes it matter: a fork's
pull_requestrun gets no secrets, which is why the review workflow is safe. Butissuesandissue_commentevents run in this repository's context and do receive repository secrets. So those runs hadCLAUDE_CODE_OAUTH_TOKENsitting in the environment.And this isn't the usual prompt-injection story. That workflow is built to carry out the instructions in the comment that tagged it. A stranger writing
@claude <instructions>isn't sneaking past anything — they're using it as designed. They just aren't us.Anyone with a GitHub account could open an issue titled
@claude ...and get a job running against our Claude subscription, with the token in reach.The fix
Gates on
author_association—OWNER,MEMBER,COLLABORATOR. Those are people with org membership or explicit repo access.CONTRIBUTORis deliberately excluded: it only means someone had a single commit merged, which is not the same as being trusted with a credential.The association is checked per event, against the account that actually triggered it. This is the subtle bit — my first attempt was wrong. On
issue_commentthe payload carries bothcomment.author_association(the commenter) andissue.author_association(whoever opened the thread). A single shared OR across event types would have let an untrusted account comment@claude ...on an issue one of us opened, and pass the gate. Each event branch now checks its own actor.Also included
timeout-minutes: 15— there was no timeout, so a wedged run could bill against the Max subscription for the full 6-hour default.concurrencygroup per issue/PR, withoutcancel-in-progress. Unlike a stale PR review, a superseded@claudetask is still work somebody asked for, so these queue rather than get killed.id-token: write. That's only needed for OIDC federation; on the static-token path it's dead weight.Was it abused?
No. I checked every run of this workflow — all legitimate internal use, most recently 2026-06-23. Repo has 0 forks. This is a hole that was open, not one that was used.
Still worth doing separately
claude.ymlis onkeepsimple-cms-new,uxcat-api, andlibrary. Those are private and everyone with access is trusted, so the exposure is very different — but the same patch applies.Test plan
pull_request, same-repo) — confirm it still runs green.@claudehere as an OWNER and confirm the job still fires.CONTRIBUTOR/NONEmention does not start a run.