ci: fail workflow-lint on unpinned action references - #710
Merged
Conversation
#707 documented the SHA-pin convention but nothing checked it, so an edit reverting a pin to `@v7` still merged. That revert is the natural thing to type: it is what upstream READMEs show. actionlint cannot cover this. Its two `uses:` rules validate format and flag outdated popular actions, and both accept `@v4` and `@main`. Hence a grep, styled after version-check.yml, rather than a config knob that does not exist. Its own job rather than a step in the actionlint one: a pin failure reported under a check named actionlint would send whoever hits it hunting for a config that is not there. Local `uses: ./` composite actions are exempt since they have no SHA to pin. None exist yet; the exclusion is there before it is needed. Closes #709
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
#699 pinned all 71 action references to commit SHAs and #707 wrote the
convention down, but nothing enforced it. An edit reverting a pin to
uses: actions/checkout@v7still merged cleanly. That revert is the naturalthing to type: it is what upstream READMEs show and what a copied snippet
contains, so it reads as normal and reviews as harmless while the guarantee
quietly goes away for that action.
workflow-lint.ymlalready triggers onpaths: [".github/workflows/**"], whichis exactly when a pin can change and never otherwise, so the check lives there.
Closes #709
Changes
One new job in
.github/workflows/workflow-lint.yml. Nothing else changes.Why a grep rather than actionlint config. actionlint has no pinning rule.
Its checks doc lists two
uses:-related rules, "Action format inuses:" and"Outdated popular actions detection", and format validation accepts any ref
including
@v4and@main. The existingreviewdog/action-actionlintstepwill never catch this, so there is no knob to turn.
Why a separate job. Given the above, a pin failure surfacing under the check
named
Lint GitHub Actions workflows (actionlint)would send whoever hits itlooking for an actionlint config that does not exist. A distinctly named job
attributes the failure correctly.
workflow-lintis not one of the 11 requiredchecks, so adding a job name here carries no branch-protection risk.
Why not a dedicated action.
zgosalvez/github-actions-ensure-sha-pinned-actionsis maintained but small, and using a third-party action to enforce supply-chain
hygiene means pinning and Dependabot-tracking one more dependency in order to
police dependencies. The grep has no supply-chain surface of its own and matches
the hand-rolled precedent in
version-check.yml.Local
uses: ./composite actions are exempt because they have no SHA to pin.None exist today (
.github/actions/is absent); the exclusion is there beforeit is needed rather than after it blocks someone.
Testing
Red and green were both proven by extracting the check body straight out of the
workflow and running it, so the test exercises the shipped code rather than a
retyped copy of it.
Against the real tree:
Against a fixture copy with one pin reverted to
actions/checkout@v7:The predicate was also checked against the cases that could bite it. Tag refs,
branch refs, and a bare SHA with no version comment are all flagged. Correctly
pinned refs pass, subpath actions like
github/codeql-action/upload-sarifpass,uses: ./passes, and a comment line mentioninguses: x@v1passes because thepattern anchors on the start of the line.
actionlintreports the same single pre-existing shellcheck warning it reportson
mainand nothing new, so the newrun:block is shellcheck-clean. The YAMLparses with both jobs present.
No CHANGELOG entry: CI-only changes do not get one.
Type of Change
fix:)feat:)refactor:)docs:)ci:)chore:)Checklist
Closes #Ntype:*and onepriority:*labelphase:*label (or none when not roadmap work)feat/<slug>,fix/<slug>, etc.)mypy src/)ruff check .)ruff format --check .)