fix(ci-conformance): resolve the policy checkout from the OIDC job_workflow_sha claim - #75
Merged
Merged
Conversation
…rkflow_sha claim github.job_workflow_sha evaluates to the empty string in the github context, so the ref: it fed to actions/checkout took the default-branch path and every run read .github@main. The caller's SHA pin decided nothing, two runs of one product commit could disagree, and check 9 went live in four repos the instant it merged, with no adoption window (.github#73). Measured, not reasoned from the YAML: run 35103911603 printed job_workflow_sha=[] len=0 and job_workflow_ref=[] len=0 from the context while the OIDC token for the same job carried job_workflow_sha=34fde546..., the commit the caller pinned. Take the claim from the token, validate it is a 40-hex SHA, and assert after checkout that .conformance HEAD is that commit. Fails closed: a caller without id-token: write gets an error naming the missing permission, never a silent fall-back to a branch. Also drops the actionlint ignore that excused the expression. actionlint was right that the property is not in the context schema.
…in is honoured Nothing exercised this repo's own conformance gate end to end — only unit tests over the script — so a checkout landing on the wrong commit could not have been caught here. ci.yml now calls ci-conformance.yml on a fixture repo under test/fixtures/, and a following job asserts the workflow's policy-sha output equals the SHA pinned in uses:. That equality is the proof the pin is honoured; if resolution regresses to the default branch the two diverge. Both jobs are in ci-complete's needs, so the self-test can block a merge.
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.
Summary
ci-conformance.ymlpinned its own policy checkout withref: ${{ github.job_workflow_sha }}. That expression evaluates to the emptystring, and an empty
ref:isactions/checkout's default-branch path — soevery run read the deciding scripts from
.github@main, whatever was there atrun time. The caller's SHA pin decided nothing.
Two consequences, both already observed: check 9 (the theme hash,
#72) startedfailing in all four product repos the instant it merged, even though neither
pinned commit's
check-ci-conformance.pycontains that check; and two runs of anidentical product commit can disagree.
The fix takes the SHA from the OIDC token instead, where the claim is
populated, and fails closed if it cannot. It also adds the verification vehicle
this repo lacked:
.githubnow runs its own conformance gate against a fixtureand asserts the pinned ref was honoured.
Why OIDC and not the context
Measured on a run, not reasoned from the YAML (the issue asked for this
explicitly). Run
35103911603,
a throwaway caller pinned at a throwaway callee:
34fde546…is exactly the callee commit the caller pinned. So: bothjob_workflow_*are absent from thegithubcontext and present in theOIDC token.
workflow_shais populated but is the caller's commit in anotherrepository, as the existing comment already warned.
actionlintwas right all along —job_workflow_shais not in its contextschema, and the
-ignore 'property "job_workflow_sha" is not defined'line inthis repo's own
ci.ymlwas the only thing keeping that from being visible. Thatignore is removed here; it was a suppression standing in front of the defect, not
a tool gap.
Caller cost
Each calling job must add
id-token: write. It grants no repository access — itmints an identity assertion and reads nothing — and it is the only mechanism that
gives a reusable workflow its own commit. A caller that omits it now gets a hard
error naming the missing permission rather than a silent fall-back to a branch.
The
Call it with:block documents it.Linked Issue
Fixes #73
Type of Change
fix— a fleet CI gate that was not enforcing what it claimed to enforce, plusthe test vehicle that would have caught it.
Risk
Medium, and deliberately fail-closed. Every product repo's next pin bump to
this workflow must add
id-token: writeto the calling job or that job failsloudly. That is the intended trade: a caller that cannot prove which policy
commit it is running should not get a verdict. Support-plan row D-GH-4 already
tracks those bumps as born-red.
Scope note, so nobody waits on the wrong thing: this PR does not green the four
product repos. They call the old workflow at the old pin, so nothing here
changes what they execute; their red is drifted
msn-shared.css, cleared by theadoption rows (
UI-SEED-7/UI-STEM-7/UI-NIAC-9/UI-TRL-8). This PR isthe separate defect — that a new check can go fleet-wide with no adoption window.
Testing Evidence
The gate runs against the fixture, and is not vacuous. Both RED proofs taken
locally with
golangci-lint 2.13.2(CI's pinned version):The fixture's theme copy is a symlink to the canonical file, so it cannot drift
away from it silently as the canonical file changes.
Local gates:
The pin-honoured proof is SHA equality, asserted by
conformance-self-test-result: the reusable workflow now exposes apolicy-shaoutput, and the job compares it to the literal SHA in its own
uses:. Theissue's phrasing ("a fixture pinned to a commit whose script lacks a check does
not run that check") cannot be exercised in this PR — every commit carrying the
fix also carries check 9, so there is no such commit to pin. That form becomes
testable the first time a check 10 lands after this. The equality assertion tests
the same property without a fabricated fixture.
The CI run for this PR is the end-to-end evidence; both self-test jobs are in
ci-complete'sneeds:, so a regression in ref resolution blocks a merge here.Security and Release Checklist
scoped to the
ci-conformance-policy-refaudience, and never persisted.checkout landing on any other commit each abort the job.
persist-credentials: falseretained on both checkouts.//nolint, nobiome-ignore; one actionlint ignore removed.id-token: writeto the calling job.End-to-end run on this PR
Run 35104862421 —
Conformance Self-TestandConformance Self-Test Resultboth pass:This is discriminating, not a tautology:
mainis75ad8d1at the time of thisrun. The old code would have landed there — it read the default branch. The run
read
55d509d0, the commit named inuses:.