Skip to content

Default workflow template missing author trust gate — secrets exposed on public repos #1068

@davidjuarezdev

Description

@davidjuarezdev

Summary

The default workflow template in 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:

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 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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:permissionsbugSomething isn't workingdocumentationImprovements or additions to documentationp1Showstopper bug preventing substantial subset of users from using the product, or incorrect docs

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions