fix(ci): advisory reviewer could never comment on a fork PR (403) - #92
Conversation
The Reviewer workflow runs on `pull_request` and requested
`pull-requests: write` to post its advisory comment. On a PR from a fork
GitHub downgrades GITHUB_TOKEN to read-only regardless of the
`permissions:` block, so the write grant was silently dropped and
`issues.createComment` failed with "Resource not accessible by
integration" (403). Every outside contribution showed a red `review`
check and never received the advisory comment it was supposed to get.
Split into the standard trusted/untrusted pair:
* reviewer.yml (untrusted, runs PR code) now holds no write permission
at all. It stages the rendered comment plus the PR number and uploads
them as an artifact.
* reviewer-comment.yml (trusted) runs on `workflow_run` from the
default branch, where the token is writable, and posts the comment.
It never checks out, builds, or executes PR code.
Deliberately not `pull_request_target`: that grants a writable token in
the base repo's context, and checking out PR head code under it is an
RCE footgun — not a trade this repo should make.
The PR number travels inside the artifact because
`workflow_run.pull_requests` is empty for fork PRs, and is filtered to
digits on both write and read. The poster only updates a comment whose
marker it finds on a Bot-authored comment, so a contributor cannot get
the bot to overwrite a human's comment by planting the marker.
GitHub runs `run:` steps as `bash -e`, so `set -uo pipefail` never disabled errexit. A non-zero exit from signetry-reviewer (any Block verdict) aborted the step immediately and the trailing `exit 0 # advisory: never fail the PR from this job` was unreachable. The workflow header promises the job 'never merges and does not block the PR (the self-admission + test checks remain the gates)', but a Block verdict turned the check red. Add `set +e` and an explicit `|| true` so the verdict lands in the advisory comment, not in the check status. Found while testing the fork-PR comment fix: this PR touches .github/workflows/**, the reviewer returned Block, and the step died at the reviewer invocation rather than reaching its own exit 0.
|
Found a second, independent bug while testing this — pushed a fix in 3d77983. The GitHub runs exit 0 # advisory: never fail the PR from this jobSo the workflow header's promise — "Advisory only — it never merges and does not block the PR (the self-admission + test checks remain the gates)" — did not hold: any Block verdict turned the This PR is how it surfaced. It touches Fixed with Also worth logging: the reviewer's It blocked this PR with two findings pointing at:
Both are comments, in prose explaining why the trigger is deliberately avoided. The rule matches the bare string anywhere in the file rather than an actual Not fixable here (the reviewer is pinned at |
…rget (#18) Every rule in scan_ci_permissions matched raw added text, so a workflow that *documented* a risk tripped the rule that exists to catch it. Found in the field: Signetry/core#92 was returned Block with two findings pointing at these lines — # We deliberately do NOT use `pull_request_target`: that runs with a writable # here safe, unlike `pull_request_target` + checkout of PR head. — i.e. prose explaining why the trigger is deliberately avoided. The same hole applied to permissions: write-all, id-token: write and curl|sh. * Skip whole-line YAML comments. Inline trailing comments are still scanned on purpose: `#` is legal inside a quoted scalar, so stripping it by regex could hide real configuration. * ci.pull_request_target now matches the trigger rather than the string, in every form YAML allows: mapping key, `on:` scalar, inline sequence, and block sequence item. The pre-existing test only covered the `on: pull_request_target` scalar; running it caught that a key-only match would have regressed the other shapes. Two new tests: all four trigger forms, and the five comment shapes that previously produced false findings. Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Rolls out Signetry/core#92, verified there end to end before propagating. reviewer.yml runs on `pull_request` and requested `pull-requests: write`. On a PR from a fork GitHub downgrades GITHUB_TOKEN to read-only regardless of the `permissions:` block, so the grant was silently dropped and issues.createComment failed with "Resource not accessible by integration" (403). Every outside contribution showed a red `review` check and never got the advisory comment. Split into the standard trusted/untrusted pair: * reviewer.yml (untrusted, runs PR code) now holds no write permission and uploads the rendered comment + PR number as an artifact. * reviewer-comment.yml (trusted) runs on `workflow_run` from the default branch, where the token is writable, and posts it. It never checks out, builds, or executes PR code. Deliberately not `pull_request_target`: that grants a writable token in the base repo's context, and checking out PR head code under it is an RCE footgun. Also fixes a second bug found while testing the first: the advisory step's trailing `exit 0` had never run, because GitHub invokes `run:` steps as `bash -e`, so any non-zero reviewer exit (a Block verdict) aborted the step first and turned the check red — contradicting the workflow's own "never blocks the PR" contract. Bumps the signetry-reviewer pin to v0.2.0, which carries the fix for CI rules firing on YAML comments (a workflow that documented a risk tripped the rule meant to catch it). Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Rolls out Signetry/core#92, verified there end to end before propagating. reviewer.yml runs on `pull_request` and requested `pull-requests: write`. On a PR from a fork GitHub downgrades GITHUB_TOKEN to read-only regardless of the `permissions:` block, so the grant was silently dropped and issues.createComment failed with "Resource not accessible by integration" (403). Every outside contribution showed a red `review` check and never got the advisory comment. Split into the standard trusted/untrusted pair: * reviewer.yml (untrusted, runs PR code) now holds no write permission and uploads the rendered comment + PR number as an artifact. * reviewer-comment.yml (trusted) runs on `workflow_run` from the default branch, where the token is writable, and posts it. It never checks out, builds, or executes PR code. Deliberately not `pull_request_target`: that grants a writable token in the base repo's context, and checking out PR head code under it is an RCE footgun. Also fixes a second bug found while testing the first: the advisory step's trailing `exit 0` had never run, because GitHub invokes `run:` steps as `bash -e`, so any non-zero reviewer exit (a Block verdict) aborted the step first and turned the check red — contradicting the workflow's own "never blocks the PR" contract. Bumps the signetry-reviewer pin to v0.2.0, which carries the fix for CI rules firing on YAML comments (a workflow that documented a risk tripped the rule meant to catch it). Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Rolls out Signetry/core#92, verified there end to end before propagating. reviewer.yml runs on `pull_request` and requested `pull-requests: write`. On a PR from a fork GitHub downgrades GITHUB_TOKEN to read-only regardless of the `permissions:` block, so the grant was silently dropped and issues.createComment failed with "Resource not accessible by integration" (403). Every outside contribution showed a red `review` check and never got the advisory comment. Split into the standard trusted/untrusted pair: * reviewer.yml (untrusted, runs PR code) now holds no write permission and uploads the rendered comment + PR number as an artifact. * reviewer-comment.yml (trusted) runs on `workflow_run` from the default branch, where the token is writable, and posts it. It never checks out, builds, or executes PR code. Deliberately not `pull_request_target`: that grants a writable token in the base repo's context, and checking out PR head code under it is an RCE footgun. Also fixes a second bug found while testing the first: the advisory step's trailing `exit 0` had never run, because GitHub invokes `run:` steps as `bash -e`, so any non-zero reviewer exit (a Block verdict) aborted the step first and turned the check red — contradicting the workflow's own "never blocks the PR" contract. Bumps the signetry-reviewer pin to v0.2.0, which carries the fix for CI rules firing on YAML comments (a workflow that documented a risk tripped the rule meant to catch it). Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Rolls out Signetry/core#92, verified there end to end before propagating. reviewer.yml runs on `pull_request` and requested `pull-requests: write`. On a PR from a fork GitHub downgrades GITHUB_TOKEN to read-only regardless of the `permissions:` block, so the grant was silently dropped and issues.createComment failed with "Resource not accessible by integration" (403). Every outside contribution showed a red `review` check and never got the advisory comment. Split into the standard trusted/untrusted pair: * reviewer.yml (untrusted, runs PR code) now holds no write permission and uploads the rendered comment + PR number as an artifact. * reviewer-comment.yml (trusted) runs on `workflow_run` from the default branch, where the token is writable, and posts it. It never checks out, builds, or executes PR code. Deliberately not `pull_request_target`: that grants a writable token in the base repo's context, and checking out PR head code under it is an RCE footgun. Also fixes a second bug found while testing the first: the advisory step's trailing `exit 0` had never run, because GitHub invokes `run:` steps as `bash -e`, so any non-zero reviewer exit (a Block verdict) aborted the step first and turned the check red — contradicting the workflow's own "never blocks the PR" contract. Bumps the signetry-reviewer pin to v0.2.0, which carries the fix for CI rules firing on YAML comments (a workflow that documented a risk tripped the rule meant to catch it). Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Rolls out Signetry/core#92, verified there end to end before propagating. reviewer.yml runs on `pull_request` and requested `pull-requests: write`. On a PR from a fork GitHub downgrades GITHUB_TOKEN to read-only regardless of the `permissions:` block, so the grant was silently dropped and issues.createComment failed with "Resource not accessible by integration" (403). Every outside contribution showed a red `review` check and never got the advisory comment. Split into the standard trusted/untrusted pair: * reviewer.yml (untrusted, runs PR code) now holds no write permission and uploads the rendered comment + PR number as an artifact. * reviewer-comment.yml (trusted) runs on `workflow_run` from the default branch, where the token is writable, and posts it. It never checks out, builds, or executes PR code. Deliberately not `pull_request_target`: that grants a writable token in the base repo's context, and checking out PR head code under it is an RCE footgun. Also fixes a second bug found while testing the first: the advisory step's trailing `exit 0` had never run, because GitHub invokes `run:` steps as `bash -e`, so any non-zero reviewer exit (a Block verdict) aborted the step first and turned the check red — contradicting the workflow's own "never blocks the PR" contract. Bumps the signetry-reviewer pin to v0.2.0, which carries the fix for CI rules firing on YAML comments (a workflow that documented a risk tripped the rule meant to catch it). Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Rolls out Signetry/core#92, verified there end to end before propagating. reviewer.yml runs on `pull_request` and requested `pull-requests: write`. On a PR from a fork GitHub downgrades GITHUB_TOKEN to read-only regardless of the `permissions:` block, so the grant was silently dropped and issues.createComment failed with "Resource not accessible by integration" (403). Every outside contribution showed a red `review` check and never got the advisory comment. Split into the standard trusted/untrusted pair: * reviewer.yml (untrusted, runs PR code) now holds no write permission and uploads the rendered comment + PR number as an artifact. * reviewer-comment.yml (trusted) runs on `workflow_run` from the default branch, where the token is writable, and posts it. It never checks out, builds, or executes PR code. Deliberately not `pull_request_target`: that grants a writable token in the base repo's context, and checking out PR head code under it is an RCE footgun. Also fixes a second bug found while testing the first: the advisory step's trailing `exit 0` had never run, because GitHub invokes `run:` steps as `bash -e`, so any non-zero reviewer exit (a Block verdict) aborted the step first and turned the check red — contradicting the workflow's own "never blocks the PR" contract. Bumps the signetry-reviewer pin to v0.2.0, which carries the fix for CI rules firing on YAML comments (a workflow that documented a risk tripped the rule meant to catch it). Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Rolls out Signetry/core#92, verified there end to end before propagating. reviewer.yml runs on `pull_request` and requested `pull-requests: write`. On a PR from a fork GitHub downgrades GITHUB_TOKEN to read-only regardless of the `permissions:` block, so the grant was silently dropped and issues.createComment failed with "Resource not accessible by integration" (403). Every outside contribution showed a red `review` check and never got the advisory comment. Split into the standard trusted/untrusted pair: * reviewer.yml (untrusted, runs PR code) now holds no write permission and uploads the rendered comment + PR number as an artifact. * reviewer-comment.yml (trusted) runs on `workflow_run` from the default branch, where the token is writable, and posts it. It never checks out, builds, or executes PR code. Deliberately not `pull_request_target`: that grants a writable token in the base repo's context, and checking out PR head code under it is an RCE footgun. Also fixes a second bug found while testing the first: the advisory step's trailing `exit 0` had never run, because GitHub invokes `run:` steps as `bash -e`, so any non-zero reviewer exit (a Block verdict) aborted the step first and turned the check red — contradicting the workflow's own "never blocks the PR" contract. Bumps the signetry-reviewer pin to v0.2.0, which carries the fix for CI rules firing on YAML comments (a workflow that documented a risk tripped the rule meant to catch it). Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Rolls out Signetry/core#92, verified there end to end before propagating. reviewer.yml runs on `pull_request` and requested `pull-requests: write`. On a PR from a fork GitHub downgrades GITHUB_TOKEN to read-only regardless of the `permissions:` block, so the grant was silently dropped and issues.createComment failed with "Resource not accessible by integration" (403). Every outside contribution showed a red `review` check and never got the advisory comment. Split into the standard trusted/untrusted pair: * reviewer.yml (untrusted, runs PR code) now holds no write permission and uploads the rendered comment + PR number as an artifact. * reviewer-comment.yml (trusted) runs on `workflow_run` from the default branch, where the token is writable, and posts it. It never checks out, builds, or executes PR code. Deliberately not `pull_request_target`: that grants a writable token in the base repo's context, and checking out PR head code under it is an RCE footgun. Also fixes a second bug found while testing the first: the advisory step's trailing `exit 0` had never run, because GitHub invokes `run:` steps as `bash -e`, so any non-zero reviewer exit (a Block verdict) aborted the step first and turned the check red — contradicting the workflow's own "never blocks the PR" contract. Bumps the signetry-reviewer pin to v0.2.0, which carries the fix for CI rules firing on YAML comments (a workflow that documented a risk tripped the rule meant to catch it). Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Picks up the fix for CI rules firing on YAML comments — this repo's own #92 was returned a Block verdict for two comment lines explaining why it deliberately avoids pull_request_target, which is what surfaced that bug. Also brings in the new supply.dependency_skew check (lockfile changed without its manifest), advisory and MEDIUM so it withholds auto-merge without rejecting Dependabot refreshes. Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
Rolls out Signetry/core#92, verified there end to end before propagating. reviewer.yml runs on `pull_request` and requested `pull-requests: write`. On a PR from a fork GitHub downgrades GITHUB_TOKEN to read-only regardless of the `permissions:` block, so the grant was silently dropped and issues.createComment failed with "Resource not accessible by integration" (403). Every outside contribution showed a red `review` check and never got the advisory comment. Split into the standard trusted/untrusted pair: * reviewer.yml (untrusted, runs PR code) now holds no write permission and uploads the rendered comment + PR number as an artifact. * reviewer-comment.yml (trusted) runs on `workflow_run` from the default branch, where the token is writable, and posts it. It never checks out, builds, or executes PR code. Deliberately not `pull_request_target`: that grants a writable token in the base repo's context, and checking out PR head code under it is an RCE footgun. Also fixes a second bug found while testing the first: the advisory step's trailing `exit 0` had never run, because GitHub invokes `run:` steps as `bash -e`, so any non-zero reviewer exit (a Block verdict) aborted the step first and turned the check red — contradicting the workflow's own "never blocks the PR" contract. Bumps the signetry-reviewer pin to v0.2.0, which carries the fix for CI rules firing on YAML comments (a workflow that documented a risk tripped the rule meant to catch it). Co-authored-by: Binay <bkd-dotcom@users.noreply.github.com>
The bug
reviewer.ymlruns onpull_requestand requestspull-requests: writeto post its advisory comment. On a PR from a fork, GitHub downgradesGITHUB_TOKENto read-only regardless of thepermissions:block — so the write grant is silently dropped andissues.createCommentreturns:Every outside contribution has shown a red
reviewcheck and never received the advisory comment. Confirmed on #89 and #91 (both fork PRs, both 403 in the `Post / update the review comment" step).The fix
The standard trusted/untrusted split:
reviewer.ymlpull_requestreviewer-comment.ymlworkflow_runpull-requests: writeThe untrusted job stages
comment.md+ the PR number as an artifact; the trusted job downloads it and posts. Because the trusted job never checks out, builds, or executes PR code, holding a writable token there is safe.Why not
pull_request_targetIt's the one-line fix and it's the wrong one: it grants a writable token in the base repo's context, and checking out PR head code under it is a well-known RCE footgun. Not a trade a change-control product should make.
Hardening details
workflow_run.pull_requestsis empty for forks) and is filtered to digits on both write and read.eval, so a crafted diff cannot escalate past comment text.Verification
actionlintclean on both files.permissionsverified ascontents/checks: read(untrusted) andpull-requests: write(trusted).reviewer-comment.ymlis on the default branch (workflow_runalways loads from default) — I'll verify with a test PR immediately after merge and report back here.Same bug exists in 9 other Signetry repos (plugins, codex, cursor, precommit, claude-code, eval, signetry, github-app, action); rolling this out there once it's proven here.