anthropics / anthropics/claude-code-action
Default workflow template missing author trust gate — secrets exposed on public repos
- Dominant language
- TypeScript
- Stars
- 8.9k
- Forks
- 2.1k
- Avg merge
- 3d 9h
- Merged PRs (30d)
- 10
Description
## Summary
The default workflow template in [`examples/claude.yml`](https://github.com/anthropics/claude-code-action/blob/main/examples/claude.yml) (also generated by `/install-github-app`) has no `author_association` check in its `if:` condition. On public repositories, **any user** who can open an issue or leave a comment containing `@claude` can trigger the workflow, which:
1. Runs on the repo's GitHub Actions runner
2. Accesses `secrets.ANTHROPIC_API_KEY` or `secrets.CLAUDE_CODE_OAUTH_TOKEN`
3. Requests an OIDC token (`id-token: write`)
## Current behavior
The `if:` condition only checks whether the event body contains `@claude`:
```yaml
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
```
## Docs vs reality
The [security docs](https://github.com/anthropics/claude-code-action/blob/main/docs/security.md) state:
> **Repository Access**: The action can only be triggered by users with write access to the repository
The action itself may perform permission checks internally, but the **workflow job still starts** — the runner spins up, checks out the repo, and the step receives the secret before the action can reject the user. This means:
- API keys / OAuth tokens are exposed to the runner environment regardless
- Runner minutes are consumed
- The OIDC token is available
## Suggested fix
Add an `author_association` trust gate to the default template:
```yaml
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) &&
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association))
```
This prevents the job from starting at all for unauthorized users, which is the only way to protect secrets from being passed to the runner.
## Impact
Every public repository that uses the default template from `examples/claude.yml` or `/install-github-app` is affected.
Contributor guide
Research direction
Start with examples/claude.yml and trace how /install-github-app generates or installs the default workflow. Review each event branch in the if: condition and add the specified author_association trust gate consistently. Done means unauthorized issue and comment authors cannot start the job, while trusted authors retain the existing @claude triggers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github-actions
- Domain
- ci-cd, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 62/100