Ci: keep the action pinning a pure pin — walk back two unauthorised major bumps (stacked on #505) - #613
Open
AmaadMartin wants to merge 2 commits into
Conversation
added 2 commits
August 3, 2026 21:05
The pinning pass converged csat.yml's actions/github-script on the v7 commit already used by auto-assignment.yml. That is a major-version bump smuggled into a pin-only change: the two are different revisions of the action, not two names for the same one (v6's action.yml declares "using: node16", v7's declares "using: node20"). Pinning exists to freeze what CI already runs so a moved tag cannot swap it out. csat.yml runs github-script v6 today, so the correct pin is the commit its v6 tag resolves to -- d7906e4ad0b1822421a7e6a35d5ca353c962f410, release v6.4.1 -- which keeps the step byte-identical to what it executes now while making it immutable. Upgrading the action is a separate, reviewable change. Nothing is lost by waiting: since 2024-06-30 the runner already executes node16 actions on Node 20 by default. Resolved via the tags REST endpoint, which dereferences to the commit. The v6 tag here is annotated, so repos/actions/github-script/git/ref/tags/v6 returns the tag object 00f12e3e20659f42342b1c0226afda7f7c042325 instead -- pinning that value would not resolve to a runnable action. The checkout bumps in this branch are unaffected: those were explicitly authorised to converge the stale v3/v4 references on the v6 the other four workflows already use.
Second of the two unauthorised upgrades the pinning pass folded into this stack. auto-assignment.yml runs actions/checkout@v4 today; pinning it at the v6 commit freezes it onto a different major (v4 declares "using: node20", v6 declares "using: node24") under a diff that otherwise reads as purely mechanical. Convergence on one checkout SHA is a maintenance nicety, not a reason to move a workflow's runtime inside a pin-only change. Pin at the commit its own v4 tag resolves to, 11d5960a326750d5838078e36cf38b85af677262 (release v4.4.0), so the step keeps executing exactly what it executes now. This leaves the stack with exactly one upgrade, and it is the one the task asked for by name: the stale actions/checkout@v3 in csat.yml, which is kept at v6. That reference is three majors behind and still declares node16; pinning it in place would cement precisely the staleness this work exists to remove, so the rule applied across all 12 references is "pin at the major the reference runs today, upgrade only where the task named it".
This was referenced Aug 4, 2026
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.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
Closes: #issue_number
Related: #issue_number
Problem: Every GitHub Action in
.github/workflows/is referenced by a floatingmajor tag (
@v6,@v5,@v4,@v3). Git tags are mutable. Whoever controls an actionrepository — or anyone who compromises it — can retag
v6onto arbitrary code, and thenext adk-js CI run executes that code with no diff, no review and no notification. These
jobs run with a
GITHUB_TOKEN, andrelease-please.ymladditionally runs withcontents: writeand a release token.This is not a theoretical hazard, and there is direct evidence in the sibling repo.
adk-pythonpinnedactions/checkoutatdf4cb1c069e1874edd31b4311f1884172cec0e10(release v6.0.3). The
v6tag today resolves tod23441a48e516b6c34aea4fa41551a30e30af803(release v6.1.0) — the tag moved out fromunder the version that was reviewed. That movement is exactly what a SHA pin makes
visible instead of silent.
GitHub's own hardening guidance is to
"Pin actions to a full length commit SHA",
because a bad actor "would need to generate a SHA-1 collision for a valid Git object
payload". OpenSSF Scorecard enforces the same rule as its
Pinned-Dependenciescheck.Solution: Reference every action by its full 40-character commit SHA with a trailing
# vNcomment, so the SHA is what GitHub executes and the version stays readable. Thestack rewrites all 12
uses:references across the six workflow files — 12 modifiedlines, zero added or deleted lines, no file outside
.github/workflows/touched.Distinct pins after the stack, with the concrete release each floating tag resolved to:
actions/checkoutv6d23441a48e516b6c34aea4fa41551a30e30af803actions/checkoutv411d5960a326750d5838078e36cf38b85af677262actions/setup-nodev6249970729cb0ef3589644e2896645e5dc5ba9c38actions/setup-pythonv5a26af69be951a213d495a4c3e4e4022e16d87065actions/setup-gov540f1582b2485089dde7abd97c1529aa768e1baffactions/github-scriptv7f28e40c7f34bde8b3046d885e986cb6290c5673bactions/github-scriptv6d7906e4ad0b1822421a7e6a35d5ca353c962f410googleapis/release-please-actionv45c625bfb5d1ff62eadeeb3772007f7f66fdcf071One rule, and the one exception. A pin should freeze what CI already runs. #505
broke that in two places by folding major-version upgrades into the pin, which makes a
diff that looks mechanical quietly change which action executes. This PR walks both back
so a single rule now holds across all 12 references — pin at the major the reference
runs today; upgrade only where the task named it. The result: 11 of 12 pins are exact
no-ops, and the stack contains exactly one upgrade.
The two lines this PR changes:
csat.ymlactions/github-script: v7 → pinned at v6. Chore(ci): pin every GitHub Actions uses: to a full commit SHA #505 converged it on thev7 commit used by
auto-assignment.yml. Those are different revisions of the action,not two names for one (v6's
action.ymldeclaresusing: node16, v7's declaresusing: node20).csat.ymlruns v6 today, so the behaviour-preserving pin isd7906e4ad0b1822421a7e6a35d5ca353c962f410(v6.4.1). Nothing is lost by waiting:since 2024-06-30 the runner already executes
node16actions on Node 20 bydefault.
auto-assignment.ymlactions/checkout: v6 → pinned at v4. Same defect: theworkflow runs
checkout@v4today (v4 declaresusing: node20, v6 declaresusing: node24), so Chore(ci): pin every GitHub Actions uses: to a full commit SHA #505's v6 SHA silently moved its runtime. Pinned at11d5960a326750d5838078e36cf38b85af677262(v4.4.0). Converging everycheckoutonone SHA is a maintenance nicety, not a reason to change a runtime inside a pin-only
change — and this workflow reacts to every opened issue and PR.
The one upgrade that stays —
csat.ymlactions/checkoutv3 → v6. This one isdeliberate and is called out rather than buried: the task asked for it by name ("bump the
stale
actions/checkout@v3up to the same v6 the other workflows use"). It is also theonly reference where pinning in place would be actively harmful —
checkout@v3is threemajors behind and still declares
using: node16, so freezing it would cement exactly thestaleness this work exists to remove. It is a no-input checkout whose only job is to make
./.github/scripts/csat.cjsreadable by the next step, and v6 is already what four otherreferences run on
ubuntu-latest,windows-latestandmacos-latest.Trade-off accepted:
actions/checkouttherefore resolves to two SHAs across the repo(v6 ×4, v4 ×1) instead of one. That is the deliberate cost of keeping this a pin, not an
upgrade PR. Bumping
auto-assignment.ymlto v6 is a one-line follow-up whenever themaintainers want it.
Annotated-tag trap (why these SHAs and not the obvious ones). For
actions/github-script@v6andgoogleapis/release-please-action@v4the tag isannotated, so
gh api repos/OWNER/REPO/git/ref/tags/vN --jq '.object.sha'returns atag object SHA —
00f12e3e20659f42342b1c0226afda7f7c042325and8b8fd2cc23b2e18957157a9d923d75aa0c6f6ad5respectively — not a commit. Pinning eithervalue yields a reference that does not resolve to a runnable action. Every SHA here was
resolved with the
tagsREST endpoint, which dereferences to the commit, then confirmedto be a real commit in that repository (commands and output below).
Deviations from the original task text, called out for review:
actions/checkout@v4andactions/github-script@v7inauto-assignment.yml. The task says pin everyaction, so the stack covers 12 references, not 10.
actions/checkout@v3incsat.ymlis bumped to v6 — the one upgrade in the stack,authorised by the task text and justified above.
actions/checkout@v4inauto-assignment.ymlis pinned at v4, not bumped to v6.The task did not ask for this one, so it is not taken; the earlier plan's
"every
checkouton one SHA" invariant is knowingly given up to keep the change apure pin.
actions/github-scriptincsat.ymlis pinned at v6, not bumped to v7. Also notasked for, and a real runtime delta.
Out of scope, deliberately: adding
.github/dependabot.ymlto maintain these pins, a CIguard rejecting future mutable-tag references, and least-privilege
permissions:blocks.Each is queued separately.
Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change. — Not applicable: zero new lines
of executable code. This is declarative CI configuration. A spec asserting the
contents of a YAML file that sits next to it is review noise with no regression
value; a genuine repo-wide "no mutable tag" guard is a separate queued task. No
vitestspec, fixture or script was added.[x] All unit tests pass locally. — The repo suite cannot be affected by a workflow-file
edit, so it was not run. What was run is the verification below, including the one
repo gate that actually reads YAML (secretlint).
Verification run locally on the pushed commit, from the repo root:
Every SHA was independently re-resolved from its tag at implementation time and confirmed
to be a commit, rather than trusted from the plan:
All eight matched the values in the stack, so no upstream tag moved between planning and
implementation. The runtimes each action declares were read from
action.ymlat thepinned commits, and are the basis for the "real runtime delta" claims above:
checkoutv3node16/ v4node20/ v6node24;github-scriptv6node16/v7
node20.Manual End-to-End (E2E) Tests:
These files execute on GitHub's runners, so CI is the only true end-to-end proof — but it
covers only part of the change, and the split is worth stating plainly.
Exercisable by CI on a PR — pins #3, #4, #5, #8, #10, #11, #12: the
validationworkflow (
checkout,setup-node,setup-python) across the ubuntu/windows/macosmatrix,
License Header Check(checkout), andCross-Language Tests(checkout,setup-node,setup-go). To confirm the pin is what actually ran, open a run's firststep and check that the "Download action repository" line shows the SHA, not a tag.
Not exercisable on a PR — pins #1, #2, #6, #7, #9:
auto-assignment.ymlandcsat.ymltrigger on issue/PR
opened/closedevents in the base repo, andrelease-please.ymltriggers on push to
main. None can be fired from a pull request and no attempt was madeto fire them artificially. They are covered by the static verification above (SHA↔tag
resolution plus the commit check). This is also the group with the least CI coverage, and
it is the reason the two walk-backs in this PR matter: after them, four of these five
pins are exact no-ops, and the single remaining behavioural change is
csat.yml'sno-input
checkoutv3→v6.Note on CI for this PR specifically:
validation.yamltriggers onpull_request: branches: [main]. Because this PR is stacked and targetsfix/pin-github-actions-to-commit-shas, those workflows do not trigger here — onlyauto-assignran, which is not validation. They run on #505 and will run again once thestack lands on
main. CI for this PR is therefore absent, not green, and thefollowing was run locally instead, on the exact pushed commit
e3fa5874:No unit tests were run, for the reason given above: the change contains no executable
code, and a workflow-file edit cannot affect the TypeScript suite.
buildandlintarereported here as evidence that the pushed commit is sound, not because they exercise the
change.
Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas. — The
# vNcomment on each pin is the entire documentation surface; the reasoning lives here and
in the commit message rather than in the workflow files.
[x] I have added tests that prove my fix is effective or that my feature works. — No
executable code, so no unit test; proven instead by the pin/format/YAML/secretlint
verification above and by CI on the workflows a PR can trigger.
[x] New and existing unit tests pass locally with my changes.