Skip to content

fix: make vouched_ci approvals work from the web dyno and verify ids match logins - #149

Merged
MarshallOfSound merged 2 commits into
mainfrom
vouched-ci-fixes
Oct 10, 2026
Merged

MarshallOfSound merged 2 commits into
mainfrom
vouched-ci-fixes

Conversation

@claude

@claude claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Requested by Samuel Attard · Slack thread

Before: As deployed after #147, the vouched_ci webhook handler never approves anything. IS_DRY_RUN is !process.argv.includes('--do-it-for-real-this-time'), and the web dyno is started as node lib/index.js (that flag only exists for the permissions cron), so the handler always stops at the "would approve run" log line, and the token minted by getVouchedCIOctokit only asked for actions: read and could not have approved a run anyway. Separately, a vouched_ci entry's id was never checked against its login: a mistyped id, a rename or a deleted account would silently never match a triggering_actor (or, if the id belonged to someone else, would be pointing at the wrong account) with nothing in CI to catch it.

After: The handler approves runs that pass the per-commit verification, and the vouched_ci token always requests actions: write. The permissions run (dry run included, i.e. the .permissions "Sheriff Dry Run" check) resolves every vouched_ci id through GET /user/{account_id} and fails with an error naming the entry when the id does not exist or its login does not match the configured login case-insensitively.

How: Two commits. The first removes the IS_DRY_RUN gate from the workflow_run.requested handler in src/index.ts (no other webhook handler gates on it) and hardcodes actions: 'write' in getVouchedCIOctokit with a comment explaining why the web server is not a dry-run context for this handler. The second adds verifyVouchedUsers(vouched, fetchUserById) to src/vouched-ci.ts, a small helper with an injected lookup so it is unit tested (match, case-difference match, login mismatch, 404, multiple bad entries, non-404 errors propagate), and calls it from main() in src/permissions/run.ts right after the org's octokit is created, next to the other GitHub-backed checks rather than in the offline validateConfigFast. A non-empty result throws, which main().catch turns into process.exit(1). README documents the check.

Notes:

  • Out of scope but worth knowing: getAuthNarrowing still narrows the web dyno's general token to read-only because IS_DRY_RUN is always true there. The pre-existing repos.addCollaborator / repos.removeCollaborator calls in src/index.ts run through that read-narrowed token, which is a separate, pre-existing issue that this PR deliberately does not touch (fixing it by changing getAuthNarrowing or the start script would widen the whole web dyno's token).
  • The id lookup uses the org's regular installation token; GET /user/{account_id} needs no extra App permission.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Wd5WZCENbhvKnJi2EMqrbi


Generated by Claude Code

…ry run

`IS_DRY_RUN` is derived from the `--do-it-for-real-this-time` argv flag,
which only the permissions cron is started with. The webhook server runs as
`node lib/index.js`, so `IS_DRY_RUN` is always true there and the
`workflow_run.requested` handler added in #147 could never get past its
"would approve run" log line, and the vouched_ci installation token was
minted with `actions: read` and could not have approved anything anyway.

Drop the guard and always request `actions: write` for that token; no other
webhook handler gates on `IS_DRY_RUN`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wd5WZCENbhvKnJi2EMqrbi
…sions run

The webhook handler only vouches for a user whose numeric id and login both
match a `vouched_ci` entry, so an entry whose id no longer resolves to its
login (typo, rename, deleted account) silently never approves anything, or
worse was meant for a different account. Resolve each id through
`GET /user/{account_id}` while processing the organization in the
permissions run and fail the run, dry run included, when the login does not
match case-insensitively or the id does not exist, so the .permissions CI
check catches it before merge.

The comparison lives in `verifyVouchedUsers` with an injected lookup so it
is unit tested; the GitHub call stays in run.ts next to the other
GitHub-backed checks rather than in the offline `validateConfigFast`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wd5WZCENbhvKnJi2EMqrbi
@claude
claude Bot requested a review from a team as a code owner September 10, 2026 21:40
@claude
claude Bot requested a review from MarshallOfSound September 10, 2026 21:41
@MarshallOfSound
MarshallOfSound enabled auto-merge (squash) September 10, 2026 22:01
@MarshallOfSound
MarshallOfSound merged commit 5bb7ff2 into main Oct 10, 2026
6 checks passed
@MarshallOfSound
MarshallOfSound deleted the vouched-ci-fixes branch October 10, 2026 11:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants