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
- A trusted collaborator (OWNER/MEMBER/COLLABORATOR) comments
@fro-bot on a fork PR.
- The job
if: passes — author_association authenticates the commenter, not the PR head code, and the fork guard is blind here.
- Checkout pulls
refs/pull/<n>/head — the fork's code — with secrets.FRO_BOT_PAT.
- 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.
- 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.
Summary
The example Fro Bot workflow (
docs/examples/fro-bot.yaml) has a HIGH-severity secret-exfiltration vector onissue_commentevents against fork PRs. Anyone copying the example inherits it.The vulnerability
issue_commentevents do not populategithub.event.pull_request— a PR comment is exposed only asgithub.event.issue.pull_request. The job's fork guard checksgithub.event.pull_request.head.repo.fork, which is null/absent forissue_comment, so the fork guard does not apply to PR comments.The checkout step then resolves PR-head code for comment-on-PR events:
Attack path
@fro-boton a fork PR.if:passes —author_associationauthenticates the commenter, not the PR head code, and the fork guard is blind here.refs/pull/<n>/head— the fork's code — withsecrets.FRO_BOT_PAT.FRO_BOT_PAT,OPENCODE_AUTH_JSON,OPENCODE_CONFIGpresent.Severity is HIGH (potentially critical):
FRO_BOT_PATis a long-lived PAT for thefro-botuser, 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 infro-bot/.github's ownfro-bot.yaml).@fro-botmentions 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_TOKENto callgh api repos/{owner}/{repo}/pulls/{n}, confirmhead.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_requestguard from!head.repo.forkto an explicithead.repo.full_name == github.repositorycheck, and addingpersist-credentials: falseto 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.