fix(ci): SHA-pin pages.yml actions and refresh stale standards reusable pins#293
Merged
Conversation
Follow-up to #292, which greened the SPDX arm of `Workflow security linter` and removed the retired scorecard-enforcer.yml. Clearing those revealed the two remaining governance failures. Opened as a new PR off main per the repo's squash-merge convention (.claude/CLAUDE.md). 1. `Workflow security linter` — action pinning pages.yml held the only 4 tag-ref `uses:` in the repo (of 66). It had never actually run -- its single run ever was the BROKEN-M1 startup_failure -- so no linter had ever seen it. actions/checkout -> de0fac2e (v6.0.2), the pin 41 other workflows here already use actions/upload-pages-artifact -> 56afc609 (v3.0.1), = current @V3 actions/deploy-pages -> d6db9016 (v4.0.5), = current @v4 The latter two are pin-only: those SHAs are exactly what the tags resolve to today. checkout moves v4 -> v6.0.2 for repo consistency; pages.yml is the only container job here, so this is the first time that pin runs inside idris2-pack. Preconditions checked before pinning it green: ddraig-ssg is public with Ddraig.idr at root, and Pages is build_type=workflow on boj-server.net. 2. `Check Workflow Staleness` — reusable pin recency The gate allows <=50 commits behind standards HEAD OR <=14 days old; d135b05 was 75 commits / 26 days behind, outside on both arms. Refreshed to 8813ecf, scoped to exactly the two reusables the gate names -- check-workflow-staleness.sh inspects only governance-reusable, hypatia-scan-reusable and scorecard-reusable, which is why mirror.yml sits at the same d135b05 and was NOT flagged. Left alone deliberately. Measured before moving: * governance-reusable: same 10 jobs before and after, so no new required check appears. The three gates that change behaviour were each run locally against this tree -- Guix primary / Nix fallback -> PASS (guix.scm present; the Nix fallback was never required, so the absent flake.nix is a non-issue) Code quality + docs -> PASS (README + LICENSE + CONTRIBUTING) banned languages + Groovy -> 0 tracked .groovy/.gradle files Both fetch their scripts from standards@main rather than from the pin, so these are the scripts CI will actually run. * scorecard-reusable: the whole d135b05..8813ecf diff is an SPDX correction (PMPL-1.0-or-later -> MPL-2.0) plus timeout-minutes: 15. Worth stating because scorecard.yml has no `pull_request` trigger, so this PR's checks do not exercise it; the diff is the evidence, not a green tick. Its first real run is the next push to main. Local verification against the real gate scripts: check-workflow-staleness.sh -> exit 0 66/66 `uses:` SHA-pinned, 25/25 workflows carry SPDX + top-level permissions, all 25 parse as YAML.
`Deploy to Cloudflare Workers` fails on main with:
The action actions/checkout@v4 is not allowed in hyperpolymath/boj-server
because all actions must be pinned to a full-length commit SHA.
This is GitHub's own `sha_pinning_required: true` repo policy, which was
deliberately preserved when allowed_actions moved selected -> all. Unlike
BROKEN-M1 it is a *correct* refusal: the action really was unpinned.
Worth recording how it was found, because two linters missed it. Both the
`Workflow security linter` and my local replication of it match `uses:`
only at the start of a line (after indentation), so both see
- name: Checkout
uses: actions/checkout@v4 # caught
but not the inline list-item form actually used here:
- uses: actions/checkout@v4 # MISSED
That single blind spot is why governance reported exactly 4 unpinned
actions (all in pages.yml) while the repo in fact had 5, and why the
scan undercounted `uses:` lines 66 vs the true 94. GitHub's policy engine
caught what the linter could not. The upstream pattern in
hyperpolymath/standards has the same gap and should be widened
separately; this commit fixes the instance.
Pinned to de0fac2e (v6.0.2), the same checkout every other workflow in
this repo now uses.
Verified with a corrected pattern matching BOTH forms: 0 of 94 `uses:`
lines are unpinned.
Not addressed here (no CI impact, flagged for follow-up): the deploy step
runs `npx wrangler@latest`, which is an unpinned floating dependency in a
job that holds CLOUDFLARE_API_TOKEN.
hyperpolymath
marked this pull request as ready for review
July 21, 2026 13:06
🔍 Hypatia Security ScanFindings: 220 issues detected
View findings[
{
"reason": "Issue in build.yml",
"type": "missing_timeout_minutes",
"file": "build.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in pages-deploy.yml",
"type": "missing_timeout_minutes",
"file": "pages-deploy.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in push-email-notify.yml",
"type": "missing_timeout_minutes",
"file": "push-email-notify.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "Issue in instant-sync.yml",
"type": "secret_action_without_presence_gate",
"file": "instant-sync.yml",
"action": "peter-evans/repository-dispatch",
"rule_module": "workflow_audit",
"severity": "high"
},
{
"reason": "Issue in codeql.yml",
"type": "codeql_missing_actions_language",
"file": "codeql.yml",
"action": "flag",
"rule_module": "workflow_audit",
"severity": "medium"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server/boj-server/cartridges/bofig-mcp/adapter/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server/boj-server/cartridges/hesiod-mcp/adapter/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server/boj-server/cartridges/academic-workflow-mcp/adapter/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server/boj-server/cartridges/sanctify-mcp/adapter/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
},
{
"reason": "TypeScript file detected -- banned language",
"type": "banned_language_file",
"file": "/home/runner/work/boj-server/boj-server/cartridges/fireflag-mcp/adapter/mod.ts",
"action": "flag",
"rule_module": "cicd_rules",
"severity": "critical"
}
]Powered by Hypatia Neurosymbolic CI/CD Intelligence |
hyperpolymath
added a commit
that referenced
this pull request
Jul 21, 2026
…p_failure) (#294) Two independent fixes, both **measured on `main`** after #293 landed. ## 1 · `Scorecards supply-chain security` — the last `startup_failure` Re-pinning `scorecard-reusable` in #293 did **not** fix it. That is the useful result: it proves the stale pin was never the cause. The real cause is the caller's permission block: ```yaml jobs: analysis: permissions: security-events: write id-token: write # <- `contents` omitted ``` Declaring `permissions:` at **job** level sets every *unlisted* scope to `none`, and a called workflow can never hold more than the calling job grants it. `scorecard-reusable` declares top-level `permissions: contents: read` and its first step is `actions/checkout` — so it was being handed **`contents: none`**. The call died at startup with zero jobs, which is exactly why there is no log to read and why it looked inscrutable. **Confirmed against the green sibling.** `boj-server-cartridges`' `scorecard.yml` lists all three — `contents: read`, `security-events: write`, `id-token: write` — and its runs succeed. This one omitted the first. This is root cause **(A)** of the estate's two `startup_failure` shapes — *reusable permission escalation* — and is unrelated to BROKEN-M1, which was the other. ## 2 · Revert `pages.yml` / `pages-deploy.yml` checkout to the v4 SHA #293 pinned these to `de0fac2e` (v6.0.2). On reflection that shipped an unvalidated version change: - Both workflows are **`push: [main]`-only** — no `pull_request` — so no PR can exercise them. The bump's first execution is on `main`. - `pages.yml` is this repo's **only container job** (`idris2-pack`) and has **never completed a run**. checkout v6 requires node24 injected into the container; whether that works against this image is precisely what could not be observed. `11d5960a` is exactly what `actions/checkout@v4` resolves to today, so this is a **pure pin** — the linter only requires a full-length SHA, not a particular version. A considered v4 → v6 bump belongs in a change where it can be watched. ## Verification - 0 of 94 `uses:` unpinned (scanning **both** the bare and `- uses:` forms — the latter is invisible to the standards linter) - all three files parse as YAML ## State of `main` after #293 `Governance` is **SUCCESS** — all 9 jobs green, the two that were red are fixed. Remaining red on `main`: | Check | Cause | Fixed by | |---|---|---| | `Scorecards supply-chain security` | `contents: none` to the called workflow | **this PR** | | `deploy` (Cloudflare) | `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID` **absent from repo secrets** — confirmed in the log: *"necessary to set a CLOUDFLARE_API_TOKEN environment variable"* | needs a human: add secrets or retire the workflow | | `SonarQube` | `SONAR_TOKEN` exists but `HTTP 403`; the SonarCloud project itself is fine (badge endpoint 200), so the token is expired/wrong-scope | needs a human: regenerate in SonarCloud | 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Clears the last two red
governancejobs. Follow-up to #292 — opened as a new PR offmainper this repo's squash-merge convention (.claude/CLAUDE.md), not as extra commits on the merged branch.Every fix was replicated locally against the real gate script before pushing.
Why these only surfaced now
governancehas 10 jobs. BROKEN-M1 (allowed_actions: selected+ emptypatterns_allowed) made the whole workflowstartup_failurewith zero jobs, so all 10 were invisible. With the policy set toall, 8 report green and these 2 report honestly. #292 then cleared the SPDX arm and the retired-workflow rule, revealing what sat behind them.1 ·
Workflow security linter— action pinningpages.ymlheld the only 4 tag-refuses:in the repo (out of 66). It had never actually run — its one and only run was the BROKEN-M1startup_failure— so no linter had ever inspected it.actions/checkoutde0fac2e(v6.0.2)actions/upload-pages-artifact56afc609(v3.0.1)@v3resolves to todayactions/deploy-pagesd6db9016(v4.0.5)@v4resolves to todayTwo are pure pins with no behaviour change.
checkoutmoves for repo consistency; worth flagging thatpages.ymlis the only container job here, so this is the first time that pin runs insideidris2-pack.Preconditions verified before pinning a never-run workflow green:
hyperpolymath/ddraig-ssgis public withDdraig.idrat root, and Pages isbuild_type=workflowonboj-server.net.2 ·
Check Workflow Staleness— reusable pin recencyThe gate allows ≤50 commits behind standards HEAD OR ≤14 days old.
d135b05was 75 commits / 26 days behind — outside on both arms. Refreshed to8813ecf.Scoped to exactly the two reusables the gate names.
check-workflow-staleness.shinspects onlygovernance-reusable,hypatia-scan-reusableandscorecard-reusable— which is whymirror.ymlsits at the samed135b05and was not flagged. It is left alone deliberately rather than swept along.Evidence, not assumption
governance-reusable— identical 10 jobs before and after, so no new required check appears. The three gates whose behaviour changes were each run locally against this tree:guix.scmpresent, so the Nix fallback (and the absentflake.nix) is moot.groovy/.gradlefilesBoth gates fetch their scripts from
standards@mainrather than from the pin, so these are the scripts CI will actually run.scorecard-reusable— the entired135b05..8813ecfdiff is an SPDX correction (PMPL-1.0-or-later→MPL-2.0) plustimeout-minutes: 15. Called out explicitly becausescorecard.ymlhas nopull_requesttrigger, so this PR's checks do not exercise it. The diff is the evidence; its first real run is the next push tomain.Local verification
check-workflow-staleness.sh→ exit 0uses:SHA-pinnedpermissions:Not in scope
SonarQubeandHypatiafail independently of these changes; triaged separately.🤖 Generated with Claude Code