Skip to content

Security: example fro-bot.yaml exposes secrets to fork PRs via issue_comment checkout #919

Description

@marcusrbrown

Summary

The example Fro Bot workflow (docs/examples/fro-bot.yaml) has a HIGH-severity secret-exfiltration vector on issue_comment events against fork PRs. Anyone copying the example inherits it.

The vulnerability

issue_comment events do not populate github.event.pull_request — a PR comment is exposed only as github.event.issue.pull_request. The job's fork guard checks github.event.pull_request.head.repo.fork, which is null/absent for issue_comment, so the fork guard does not apply to PR comments.

The checkout step then resolves PR-head code for comment-on-PR events:

ref: >-
  ${{
    (github.event.issue.pull_request && format('refs/pull/{0}/head', github.event.issue.number))
    || github.event.pull_request.head.sha
    || ''
  }}
token: ${{ secrets.FRO_BOT_PAT }}

Attack path

  1. A trusted collaborator (OWNER/MEMBER/COLLABORATOR) comments @fro-bot on a fork PR.
  2. The job if: passes — author_association authenticates the commenter, not the PR head code, and the fork guard is blind here.
  3. Checkout pulls refs/pull/<n>/head — the fork's code — with secrets.FRO_BOT_PAT.
  4. The secret-bearing agent step (and any post-checkout setup/run steps) then execute against attacker-controlled workspace content with FRO_BOT_PAT, OPENCODE_AUTH_JSON, OPENCODE_CONFIG present.
  5. A malicious fork modifies executable repo files (a local composite action, package scripts, config, prompt-injection surfaces) to exfiltrate those secrets.

Severity is HIGH (potentially critical): FRO_BOT_PAT is a long-lived PAT for the fro-bot user, so compromise persists until rotation and can cross repository boundaries.

Remediation

The minimal safe fix is to never check out PR-head code — drop the ref: PR-head expression so checkout uses the default/workflow ref (the pattern already used safely in fro-bot/.github's own fro-bot.yaml). @fro-bot mentions then operate on the default branch rather than PR-head code.

If running against same-repo PR code via mention is a desired feature, gate it behind a no-checkout preflight: use GITHUB_TOKEN to call gh api repos/{owner}/{repo}/pulls/{n}, confirm head.repo.full_name == github.repository, and only then check out the exact head SHA — gating checkout and every secret-bearing step on that result. Fork PRs must never reach a secret-bearing checkout.

Recommend also strengthening the pull_request guard from !head.repo.fork to an explicit head.repo.full_name == github.repository check, and adding persist-credentials: false to checkout as defense-in-depth.

Since this is the canonical example users copy, fixing it here prevents the vector from propagating to every downstream workflow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions