Add recipe to pin GitHub Actions to commit SHAs#169
Merged
Conversation
New recipe that replaces mutable tag/branch references in GitHub Actions uses: declarations with immutable commit SHAs. Includes static SHA mapping for common actions with GitHub API fallback, configurable official action pinning, and comprehensive test coverage.
All SHA mappings now live exclusively in known-action-shas.json. If the resource cannot be loaded, return an empty map instead of falling back to a duplicate in-code copy.
Runs weekly (Monday 06:00 UTC) and on manual dispatch. Scans the repo's own workflow files for uses: references with tags/branches, resolves each to a commit SHA via the GitHub API, and appends any new entries to known-action-shas.json (existing entries are never modified or removed). Opens a PR with the new mappings listed.
- Replace Set.of() with Collections.unmodifiableSet(new HashSet<>(Arrays.asList())) - Replace String.isBlank() with trim().isEmpty() - Replace InputStream.readAllBytes() with a ByteArrayOutputStream read loop - Remove withSuffix() call (not present on Yaml.Scalar); drop inline ref comment - Update test expectations to match (no trailing # ref comment)
Add the missing recipe catalog entry for PinGitHubActionsToSha to fix CI validation. Also apply minor style cleanups (static imports, consistent test indentation).
Load known SHAs in getInitialValue() instead of a static field, following the ScanningRecipe pattern for proper lifecycle management.
…anch refs - Replace known-action-shas.json with known-action-shas.properties to drop the Jackson dependency from PinGitHubActionsToSha - Expand from 38 to 88 well-known action mappings covering the most popular GitHub Actions across CI/CD, Docker, cloud providers, build tools, security scanning, and language setup - Remove branch references (@master) since only tags are stable - Parse GitHub API responses with a simple regex instead of Jackson - Update the update-known-shas workflow for the new properties format
Move the action-to-SHA resolution logic from the GHA workflow inline run into .github/scripts/update-known-shas.sh so it can be run locally outside of GitHub Actions. The workflow now calls the script and uses git diff to detect changes.
Resolve all major version tags (v1, v2, v3, ...) for every action in the properties file, bringing the total from 88 to 232 entries. This ensures the recipe can pin any major version reference without falling back to the GitHub API.
The script now checks all existing entries against the GitHub API and updates any whose floating tags (e.g. v4) have moved to a new commit. The workflow PR body distinguishes between updated and newly added entries.
Explains what the file contains, how to add new actions, and that header comments are preserved across regeneration by the update script.
Expand the update script to resolve all version tags (v1, v1.2, v1.2.3) for every action, not just major versions. Patch and minor versions are more likely to be pinned to a stable SHA. Also fix bash 3.2 compat by replacing mapfile with a while-read loop. Grows the properties file from 232 to 4057 entries.
Minor and patch version tags (v1.2, v1.2.3) are immutable, so skip them during drift detection. Only major version tags (v1, v2) can float to new commits. Also update the properties file header comment to accurately describe this behavior.
Expand the known-action-shas.properties from ~230 to ~9,400 entries covering popular actions across CI/build, security, deployment, Docker, GitHub/PR, linting, notifications, infrastructure, and utility categories. Each action includes all major, minor, and patch version tags. Also add a SEED_ACTIONS list to the update script so these actions are always expanded on future runs, even if not referenced in local workflow files. 24 actions hit GitHub API rate limits and will be resolved on the next run.
Produces `uses: action@sha # tag` instead of just `uses: action@sha`, matching the convention used by Dependabot, Renovate, and StepSecurity. Inlines the visitor and handles edge cases where `uses:` is the last entry in its mapping (e.g. reusable workflow calls with no siblings).
Resolve recipes.csv conflict: adopt main's class renames (drop Recipe suffix) while keeping the PinGitHubActionsToSha entry from this branch.
The shouldPinReusableWorkflowAsOnlyEntry test used openrewrite/gh-automation@main which isn't in the static SHA map, causing it to resolve via the live GitHub API. Since main is a moving target, the expected SHA drifted and the test failed. Replace with shouldPinActionAsLastEntryInMapping which uses codecov/codecov-action@v4 from the static map, still exercising the same code path (comment placement when uses: is the last entry in its mapping).
7 tasks
Adds an `includedActions` option that, when set, restricts pinning to only those actions matching one of the supplied patterns. Patterns may be `owner/repo` (exact), `owner/*` (any repo within an org), or `owner/repo/subpath` (exact match including subpath). When the allow list is non-empty, an explicit allow always wins — `pinOfficialActions` is bypassed for matched entries — so `actions/checkout` can be pinned without flipping the official-actions flag globally. Six new tests cover allow-list filtering, the org wildcard, subpath handling (both `owner/repo` matches with subpath and exact `owner/repo/subpath` patterns), pinning an official action via the allow list, and the empty-list-falls-back-to-default behavior.
Replace raw HttpURLConnection with org.openrewrite.ipc.http.HttpSender obtained via HttpSenderExecutionContextView so the recipe respects any HTTP sender configured by the host (e.g. the Moderne Platform, where direct network access may be restricted or routed through a proxy).
…eaders - Replace any same-line `# user comment` after `uses:` with `# <tag>` rather than prepending the tag, matching the convention Dependabot and Renovate read for future updates. New helper mergeTagCommentIntoPrefix encapsulates the merge logic and is used in both the in-mapping path and the doAfterVisit / Document.End path. - Add a known-action-shas entry for slsa-framework reusable workflow at v2.1.0 and a test that pins it end-to-end, exercising the `org/repo/.github/workflows/file.yml@<tag>` ref form. - Switch the GitHub REST call from the legacy `application/vnd.github.v3+json` Accept media type to `application/vnd.github+json`, consistent with the `X-GitHub-Api-Version: 2022-11-28` header already on the request. - Bump copyright header to 2026 on the three PR-added files. Three additional tests cover the new comment-merging behavior: shouldReplaceExistingInlineCommentWithVersionTag, shouldReplaceExistingInlineCommentWhenUsesIsLastEntry, and shouldPreserveCommentOnDifferentLine (which confirms standalone comments on prior lines are untouched).
Run via `./gradlew recipeCsvGenerate` so the new option from the allow-list change appears in both the description and the JSON options list, keeping the recipe catalog in sync with the recipe class.
Contributor
|
A few items I noticed while looking at this that may be worth follow-ups (not blocking IMO):
|
steve-aom-elliott
approved these changes
Apr 29, 2026
Contributor
steve-aom-elliott
left a comment
There was a problem hiding this comment.
Added a few comments above that can be decided whether they're worth exploring further, but I think it's a fairly good initial version of the recipe otherwise.
- Anchor SHA_RESPONSE_PATTERN to the start of the JSON body so nested tree.sha / parents[].sha can no longer be returned by mistake. - Pass the PR body via an env var and key the branch name on GITHUB_RUN_ID, avoiding bash command substitution on backticks in the body and same-day branch collisions.
When `uses:` references a branch (e.g. `main`, `master`), the resolved SHA is only valid as of a point in time, since branches keep moving. Emit the comment as `<branch> @ YYYY-MM-DD` so reviewers and update tools can tell when the pin was taken. Tag refs continue to round-trip as just `<tag>` so Dependabot/Renovate keep recognizing them.
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
PinGitHubActionsToSharecipe that replaces mutable tag/branchuses:references with immutable commit SHAs, complementing the existingUnpinnedActionsRecipe(which finds the problem) with an automated fixknown-action-shas.jsonwith 37 well-known action → SHA mappings (Codecov, Docker, AWS, Google, Gradle, GitHub official actions, etc.), with fallback to the GitHub API for any action not in the static mappinOfficialActionsoption (defaultfalse) controls whetheractions/*andgithub/*org actions are also pinned; original ref is preserved as an inline comment (e.g.uses: actions/checkout@b4ffde65... # v4)Test plan
shouldPinThirdPartyActionFromStaticMap— verifies static map lookup and SHA substitution with commentshouldNotPinOfficialActionsByDefault— no-op whenpinOfficialActions = falseshouldPinOfficialActionsWhenOptedIn— pinsactions/*when opted inshouldPinGitHubOrgWhenOptedIn— pinsgithub/*(e.g.codeql-action) when opted inshouldNotModifyAlreadyPinnedAction— idempotent on already-SHA-pinned refsshouldSkipLocalActions— ignores./refsshouldSkipDockerReferences— ignoresdocker://refsshouldPinMultipleThirdPartyActions— pins multiple actions in one workflowshouldIgnoreNonWorkflowFiles— only applies to.github/workflows/files viaIsGitHubActionsWorkflowshouldPinActionWithSubpath— handlesowner/repo/subpath@refcorrectlyshouldMixPinnedAndUnpinnedActions— mixed workflow with already-pinned, local, and unpinned actions