Skip to content

fix(runner-policy): auto-approve only ci-workflows release commits - #883

Merged
kyle-sexton merged 2 commits into
mainfrom
fix/runner-policy-release-commits
Oct 26, 2026
Merged

kyle-sexton merged 2 commits into
mainfrom
fix/runner-policy-release-commits

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: owner-authorized hardening of runner-policy auto-approval against imposter commits.

Summary

Auto-approval copies a reviewed contract forward onto any full SHA whose security surface matches a reviewed basis. It never checked that the SHA is a real ci-workflows release. GitHub resolves a SHA from anywhere in a repository's fork network, so a fork-only or unreleased commit with a matching surface and malicious steps would be approved. The hosted-only pr-review-cli contract forwards the REVIEWS_* OAuth tokens and API keys. Background: Chainguard, imposter commits and StepSecurity, triaging imposter commits.

Fix

  • runner-policy.mjs: after every existing decline rule, a candidate is approved only when a tag of its source repository points at it and that tag's release is immutable, published and not a prerelease. It reads GET /repos/{owner}/{repo}/git/matching-refs/tags/, peels annotated tags through git/tags/{sha}, then reads releases/tags/{tag}, all through the existing fetch seam with GITHUB_TOKEN when set. A non-release commit declines with a diagnostic; any failed request declines (fail closed). A tag with no release (404) is skipped, not treated as a release.
  • Immutability is required, not just preferred: the releases API exposes immutable, and every recent ci-workflows release has it set. A future release published without immutability will be declined and needs a reviewed contract.
  • The tree request now shares a small fetchGitHubJson helper; its error text is unchanged.
  • Existing decline rules are unchanged, and the release check runs last, so their diagnostics do not change.
  • README.md: one paragraph on the release-commit requirement.

Verification

  • node --test components/runner-policy/runner-policy.test.mjs: 365 of 365 pass (355 before, plus 10 new cases): a lightweight release-tag commit is approved; an annotated tag is peeled and approved; an annotated tag peeling to another commit, a non-release SHA with an identical surface, a tag without a release, and draft, prerelease and non-immutable releases are declined; a 500 from the tag list or the release lookup is declined.
  • The shared test mocks now serve each test's source SHAs as release commits, so the existing approval tests keep exercising approval.
  • Mutants killed: always-true release check, dropping the immutable condition, skipping the peel, and treating a 404 release as approved each fail at least one test.
  • node --test components/claude-lanes/repin-policy-lockstep.test.mjs: 23 of 23 pass.
  • npm run lint:runner-policy: passes. Biome check and markdownlint: clean.
  • Live check of the API shapes: releases/tags/v0.65.13 returns draft: false, prerelease: false, immutable: true, a missing tag returns 404, and matching-refs/tags/ returns all 109 ci-workflows tags in one response.

Related

  • #882 registered v0.65.13 (ba7af78), the SHA a local probe showed being auto-approved with no token before it was registered.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DpzEJAE4EMndcWQXiKSZYn

GitHub resolves a commit SHA from anywhere in a repository's fork network,
so a surface-matching fork-only or unreleased commit was auto-approved.
Auto-approval now also requires the candidate to be the commit of an
immutable, published, non-prerelease release tag (annotated tags peeled),
and declines when the tag or release lookup fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpzEJAE4EMndcWQXiKSZYn
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T03:58:13.459848Z 431ffb9 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

claude-security-review reviewed 230199b9ed85341bfc34f8bf228bc255717c44e2: 0 finding(s), 0 as inline comments.

Incremental security review of components/runner-policy/THREAT-MODEL.md (HEAD 230199b, since 431ffb9). I found no security issues.

The change is documentation only. It records a new requirement for auto-approving a Dependabot SHA bump of a reusable workflow. The candidate SHA must be the commit of an immutable, published, non-prerelease release tag of the source repository. Annotated tags are peeled. Any failed tag or release lookup declines approval. The same change adds a threat-model row for the fork-network or unreleased-SHA attack and notes that auto-approval now also depends on api.github.com.

  • Instruction surface: the diff does not delete, narrow or soften a control. It adds a gate, so the instruction-surface-deletion lens finds nothing to report.
  • Fail-closed behavior: the documented behavior declines on a non-release commit and on lookup errors, so the new api.github.com dependency fails closed.
  • Scope: this lane read only the documentation diff and did not audit the code in runner-policy.mjs that implements the gate. The earlier review at 431ffb9 covers the rest of the PR.
  • Protected-class list: I did not consult docs/conventions/instruction-exception-register/README.md. It was not needed, because nothing was removed.
  • Manifests, trusted paths and spend: no plugin manifests or trusted paths changed, and no spend is involved.

critical-findings: 0

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

claude-review reviewed 230199b9ed85341bfc34f8bf228bc255717c44e2: 0 finding(s), 0 as inline comments.

Incremental review of 230199b (components/runner-policy/THREAT-MODEL.md)

No findings. I checked the new text against runner-policy.mjs by reading it. I did not run any tests.

  • Auto-approval rule. The new text says approval needs an immutable, published, non-prerelease release tag, with annotated tags peeled. It also says a non-release commit, a failed tag lookup or a failed release lookup declines. This matches isImmutableReleaseCommit (runner-policy.mjs around lines 1941-1975). That function reads matching-refs/tags/, peels via git/tags/{sha}, reads releases/tags/{tag}, skips a 404, and requires draft === false, prerelease === false and immutable === true. The caller (around lines 2225-2240) declines with a diagnostic on a non-release commit or on a thrown error, and runs after the other decline rules.
  • New threat-table row. It names the same three endpoints and the same test cases the PR lists.
  • Availability note. The addition of api.github.com is correct. The release check uses the GitHub API, and it fails closed on error.

Advisory, not blocking, and not on a changed line: the trust-boundary list in the same file (THREAT-MODEL.md L48) still names only raw.githubusercontent.com as the external source of auto-approval evidence. Auto-approval now also trusts api.github.com tag and release metadata, including the immutable flag. A one-line bullet there would keep the boundary list complete. The rest of the document is consistent with the code.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 431ffb973d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread components/runner-policy/README.md Resolved
…eat model

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DpzEJAE4EMndcWQXiKSZYn
@kyle-sexton
kyle-sexton enabled auto-merge (squash) October 26, 2026 04:34
@kyle-sexton
kyle-sexton merged commit adc3f68 into main Oct 26, 2026
22 checks passed
@kyle-sexton
kyle-sexton deleted the fix/runner-policy-release-commits branch October 26, 2026 04:36
kyle-sexton added a commit that referenced this pull request Oct 31, 2026
Closes #927

## Summary

A contract-only `ci-status` re-run fails closed when GitHub's jobs API
lags on re-run jobs, which left a red `ci-status` on heads whose full
run passed. ci-workflows v0.69.0 contains the `aggregate-results` fix
(ci-workflows#929); this PR adopts it.

## Fix

- Re-pin every `melodic-software/ci-workflows/.github/actions/...` pin
in `pr-require-checks.yml` and the two `managed-files-guard` caller
components to v0.69.0 (`94f22d06b68fcfc0e293122c1cbbb6e0eba11ef3`),
using `components/claude-lanes/repin-callers.sh apply`, the script the
repin workflow runs.
- Pass `step-name: Aggregate lane results` to `aggregate-results`,
matching the step's `name:`.
- Raise the `ci-status` job `timeout-minutes` from 3 to 8: the action
can wait about 337 s, and a job that times out ends `cancelled`, which
counts as red. v0.69.0's `action.yml` recommends a calling-job timeout
of 7 for this configuration.

## Verification

- `actionlint`, `zizmor --offline` on `pr-require-checks.yml`, `npm run
lint:runner-policy`, `lint:concurrency-policy`,
`lint:github-actions-conventions` and `harness/shell/run-tests.sh
components/claude-lanes/repin-callers.test.sh` all pass.
- The acceptance re-run of a failed contract-only `ci-status` can only
be observed after merge.

## Related

- melodic-software/ci-workflows#929, #842, #883, #914
- [ci-workflows
v0.69.0](https://github.com/melodic-software/ci-workflows/releases/tag/v0.69.0)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01VcupojYKhHyDni9LuAXBuX

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

1 participant