Context
Split out of #437 per the repo-guard review on #438. Pre-existing behavior, documented but deliberately not changed there — #438 only corrected the description.
Behavior
.github/workflows/repo-guard.yml gates issue_comment on the commenter:
github.event_name == 'issue_comment' &&
(
contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) ||
contains(fromJSON('["NanluQingshi","HaveNiceDa"]'), github.event.comment.user.login)
)
The checkout step then resolves that branch to:
ref: ${{ ... || format('refs/pull/{0}/head', github.event.issue.number) }}
So any MEMBER/COLLABORATOR (or the two named external contributors) commenting on any third-party fork PR pulls that fork's head onto the self-hosted runner (vars.REPO_GUARD_RUNNER), in a job whose env carries secrets.LLM_API_KEY.
By contrast the pull_request_target path gates on the PR author and is restricted to repo branches plus the named allowlist.
Why this is not covered by the #438 opt-in
actions/checkout's assertSafePrCheckout returns early unless the event is pull_request_target or workflow_run, so the issue_comment path never hit the refusal and is unaffected by allow-unsafe-pr-checkout. #438 scopes that flag to pull_request_target only, so this path stays opted out even if the action's guard widens later.
Mitigating context
Confirmed from ceilf6/repo-guard@main's action.yml: the composite steps only ever execute from github.action_path (node "${{ github.action_path }}/scripts/review.mjs"), never from the checked-out workspace, which is passed as an env var and documented as explored "with read-only tools". So the exposure is not arbitrary code execution.
The residual risk is prompt injection against the reviewing agent — fork-authored files are read by Claude Code on a persistent host with an API key in env.
Also note the trigger requires a deliberate act by a trusted member, which is itself a form of vetting.
Options
- Leave as-is, now that it is documented in the workflow comment.
- Additionally require the PR author to pass the same allowlist on this path — needs an API lookup, since the
issue_comment payload's issue.pull_request carries only URLs and no head repo info.
- Resolve the head SHA via
gh api first and check out that fixed SHA, closing the TOCTOU gap that refs/pull/{n}/head leaves open.
Maintainer call — this is a trust decision, not a bug fix.
Context
Split out of #437 per the repo-guard review on #438. Pre-existing behavior, documented but deliberately not changed there — #438 only corrected the description.
Behavior
.github/workflows/repo-guard.ymlgatesissue_commenton the commenter:The checkout step then resolves that branch to:
So any MEMBER/COLLABORATOR (or the two named external contributors) commenting on any third-party fork PR pulls that fork's head onto the self-hosted runner (
vars.REPO_GUARD_RUNNER), in a job whose env carriessecrets.LLM_API_KEY.By contrast the
pull_request_targetpath gates on the PR author and is restricted to repo branches plus the named allowlist.Why this is not covered by the #438 opt-in
actions/checkout'sassertSafePrCheckoutreturns early unless the event ispull_request_targetorworkflow_run, so theissue_commentpath never hit the refusal and is unaffected byallow-unsafe-pr-checkout. #438 scopes that flag topull_request_targetonly, so this path stays opted out even if the action's guard widens later.Mitigating context
Confirmed from
ceilf6/repo-guard@main'saction.yml: the composite steps only ever execute fromgithub.action_path(node "${{ github.action_path }}/scripts/review.mjs"), never from the checked-out workspace, which is passed as an env var and documented as explored "with read-only tools". So the exposure is not arbitrary code execution.The residual risk is prompt injection against the reviewing agent — fork-authored files are read by Claude Code on a persistent host with an API key in env.
Also note the trigger requires a deliberate act by a trusted member, which is itself a form of vetting.
Options
issue_commentpayload'sissue.pull_requestcarries only URLs and no head repo info.gh apifirst and check out that fixed SHA, closing the TOCTOU gap thatrefs/pull/{n}/headleaves open.Maintainer call — this is a trust decision, not a bug fix.