security(ci): repo-guard's issue_comment path checks out any fork's head based on the commenter, not the PR author
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 119
- Forks
- 19
- PR merge metrics
- No merged PRs in 30d
Description
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_commentpayload'sissue.pull_requestcarries only URLs and no head repo info. - Resolve the head SHA via
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with .github/workflows/repo-guard.yml and inspect the issue_comment gate and checkout ref, then compare them with the pull_request_target path. Review the context from #437 and #438, including the documented allow-unsafe-pr-checkout scope. Done means the maintainer has selected a trust decision and the workflow reflects that decision, or the current behavior is explicitly retained.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github-actions
- Domain
- ci-cd, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100