fix(ci): pin the Hypatia scanner to a commit instead of cloning its moving tip - #747
Conversation
…oving tip `validate-hypatia-baseline` is a REQUIRED status check on roughly 120 consumer repositories, and 342 of the 349 callers pin this reusable to a 40-character SHA. That care was defeated inside the job: the "Resolve Hypatia HEAD commit" step ran `git ls-remote ... HEAD` and the clone step ran `git clone --depth 1`, so every run scanned with whatever hyperpolymath/hypatia's default branch held at that moment. A consumer could turn red with no change on its own side, and the SHA in the caller's `uses:` protected nothing about what actually executed. Now the step emits a fixed HYPATIA_PIN (hypatia main tip 0e913426e20282accb49d2fa5d14d5bedbc5a6c2, "fix(security): validate actions lock integrity (#741)", 2026-09-06) and the clone fetches exactly that commit with `git fetch --depth 1 origin <sha>` followed by `checkout FETCH_HEAD`. The cache key already embedded the resolved SHA, so it keeps working unchanged: a future bump misses the cache and rebuilds the escript by construction. The bump procedure is recorded in-file next to the pin, and a comment records why the v1.0.0 tag must never be used (not an ancestor of main, over 1,000 commits behind). No `uses:` reference changes, so `actions.lock` is untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📜 Recent review details🔇 Additional comments (5)
📝 SummarySummary by CodeRabbit
WalkthroughThe governance workflow now restricts permissions and uses a fixed Hypatia commit for cache keys and checkouts. Cache misses fetch only the pinned commit instead of the repository’s moving default branch. ChangesGovernance workflow hardening
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to The governance workflow now validates with a deterministic Hypatia revision and narrower permissions, reducing moving-branch and privilege exposure without an evidenced current-head merge blocker. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. A rabbit guards the workflow gate Comment |
…e ruleset The "Shared scanner content safety" ruleset requires a Scorecard code-scanning analysis on the PR's head or merge commit. actions/checkout on a pull_request event checks out refs/pull/N/merge, which GitHub regenerates whenever main moves, so the analysis binds to a merge commit that stops matching before the PR can merge. Checking out github.event.pull_request.head.sha instead binds the upload to the head commit, which is stable for the life of the push. codeql-action's getRef() rewrites refs/pull/N/merge to refs/pull/N/head when HEAD differs from GITHUB_SHA, so the upload lands on the head SHA without any further change.
The "Shared scanner content safety" ruleset evaluates code scanning per (tool, category). scorecard-action forces Local mode on pull_request events, so a PR upload carries only supply-chain/local while the scheduled main run carries branch-protection and online-scm as well. Every PR therefore fails with "expecting 2 results from Scorecard". Local proof with the v5.5.0 CLI: a repo-mode scan at the PR head SHA skips the repo-state checks (2 categories, no branch-protection); only a HEAD scan yields all 3. So the PR job now installs the pinned scorecard CLI (release tarball, sha256-verified), runs exactly the seven repo-level checks at repository HEAD with the policy file taken from scorecard-action's pinned SHA, and appends those two runs to the scorecard-action SARIF before the existing upload. The local category remains the scorecard-action measurement of the checked-out PR tree. The step fails unless exactly three categories result. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
SonarCloud (githubactions:S6506) flags curl -L without a protocol restriction: a redirect could in principle land on plain HTTP. Both downloads are checksum-verified afterwards, so a downgrade could not smuggle content in, but there is no reason to permit one at all. Restrict the initial request and any redirect to HTTPS. Verified locally with curl 8.14: both URLs still fetch and both sha256 checks still pass under the restricted protocol set. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tia-scanner-in-governance-reusable Bring the Scorecard head-sha fix (#745) into this branch so the required 'scorecard / Run Scorecard PR' context can be produced on this PR's head.
…to job level
SonarCloud githubactions:S8264 on this PR flags the workflow-level
`permissions:` block ("Move this read permission from workflow level to
job level"). Thirteen of the fifteen jobs already declare their own
block, which overrides the workflow-level one entirely; only
workflow-staleness and validate-hypatia-baseline inherited it.
Give those two jobs an explicit `contents: read` and delete the
workflow-level block. Neither job needs `actions: read`: the staleness
script only calls the public compare endpoint, and the Hypatia job only
checks out, caches and runs the scanner. With every job now explicit,
removing the workflow-level default cannot widen any job's token.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… map The S8264 cure (b5c729b) removed the workflow-level `permissions:` block because Sonar githubactions:S8264 flags read grants declared at workflow level. That turned two estate gates red on the same file: - the workflow-security self-lint step greps for `^permissions:` and reported "missing top-level 'permissions:' declaration" - Hypatia workflow_audit `missing_permissions` (~r/^permissions:/m) raised one new medium finding, failing Validate Hypatia Baseline `permissions: {}` satisfies both regex gates (a declaration exists) while granting nothing at workflow level, so every grant stays at job level as S8264 requires. All 15 jobs keep their job-level permissions blocks. Whether Sonar tolerates an empty map is verified live on this push; if it does not, the three gates are genuinely contradictory and that is reported, not papered over. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|



Why
validate-hypatia-baselineis a required status check on roughly 120 consumer repositories, and 342 of the 349 callers pin this reusable to a full SHA. Inside the job that pin protected nothing: the resolve step rangit ls-remote … HEADand the clone step rangit clone --depth 1, so every run scanned with whateverhyperpolymath/hypatiamain held at that moment. A consumer could go red with no change on its own side.What
HYPATIA_PIN(hypatia main tip0e913426e20282accb49d2fa5d14d5bedbc5a6c2, 2026-09-06) with the bump procedure recorded beside it.git fetch --depth 1 origin <sha>+checkout FETCH_HEADon a cache miss.permissions:is now the empty map{}and every job declares its own least-privilege block (workflow-stalenessandvalidate-hypatia-baselinegaincontents: read). Three gates disagree on how permissions must be spelled on this file — SonarCloud S8264, the workflow linter's grep, and Hypatia'smissing_permissionsrule — and the empty workflow-level map with per-job grants is the one shape all three accept; verified on this PR's own CI.v1.0.0tag: it is not an ancestor of main and is over 1,000 commits behind.No
uses:reference changes, soactions.lockis untouched.Propagation
SHA-pinned callers run the reusable as it was at their pinned SHA, so this reaches them only when re-pinned. The 41 open
chore/bump-governance-pin-to-standards-mainPRs will be re-pointed at the merge SHA of this PR.🤖 Generated with Claude Code