Skip to content

build: add workflow to restrict GitHub Action SHA pin changes#11638

Draft
Planeshifter wants to merge 1 commit intodevelopfrom
philipp/check-workflow-action-pins
Draft

build: add workflow to restrict GitHub Action SHA pin changes#11638
Planeshifter wants to merge 1 commit intodevelopfrom
philipp/check-workflow-action-pins

Conversation

@Planeshifter
Copy link
Copy Markdown
Member

Description

What is the purpose of this pull request?

This pull request:

  • Adds a new workflow, .github/workflows/check_workflow_action_pins.yml, that prevents untrusted contributors from modifying pinned GitHub Action SHAs in any file under .github/workflows/**.
  • Treats the PR author as trusted when github.event.pull_request.author_association is OWNER, MEMBER, or COLLABORATOR, and automatically passes the check for dependabot[bot] and stdlib-bot.
  • Provides a bypass for one-off legitimate changes by non-maintainer authors: a maintainer may apply the Allow Action Pin Changes label, which causes the scan to be skipped and the check to pass on re-run.
  • On failure, posts (or updates) a comment from stdlib-bot on the pull request listing the offending lines and explaining how to unblock — either by reverting the pin change or by requesting the bypass label.

Why

Every workflow file in this repo pins actions to a full commit SHA (e.g. uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2). Those pins are a supply-chain safeguard — an attacker who compromises an upstream action does not affect this repo as long as the pinned SHA is unchanged. Today, nothing programmatically prevents a PR from silently swapping one pinned SHA for another that points at a compromised commit; review is the only backstop. This workflow makes that class of change impossible to merge without explicit maintainer approval.

Design notes

  • Trigger: pull_request_target (runs from the base branch) so the check cannot be disabled or weakened by edits to this workflow file in the PR itself. No PR code is checked out — only the GitHub API is queried — so the usual pull_request_target pwn-request risk does not apply.
  • Detection: paginated call to GET /repos/{repo}/pulls/{n}/files, matches added/removed lines against ^[+-][[:space:]]*uses:[[:space:]]+[^@[:space:]]+@[0-9a-f]{7,40} in any file whose current or previous path is under .github/workflows/**. Rename-out evasion is covered via previous_filename.
  • Error handling: curl verifies HTTP 200 and the response is a JSON array before iterating; rate-limit / 5xx errors fail loud instead of silently passing.
  • Comment dedup: uses peter-evans/find-comment + peter-evans/create-or-update-comment with an HTML marker so repeated failures update a single comment instead of stacking.

Out of scope / known gaps

  • The regex targets SHA pins (@[0-9a-f]{7,40}). An untrusted contributor could in principle add uses: foo/bar@v1 (tag/branch reference) without tripping the check. This matches the stated scope (SHA pins only); code review remains the backstop for the repo's pin-only policy.
  • A GitHub repository ruleset with a "Restrict file paths" rule would be strictly stronger (server-enforced, cannot be skipped by re-running CI). This workflow is intended as the diagnostic layer that gives contributors a clear, in-PR failure message; a ruleset can be layered on top as a hard gate.

Related Issues

Does this pull request have any related issues?

No.

Questions

Any questions for reviewers of this pull request?

  • The bypass label is named Allow Action Pin Changes (mirrors the Do Not Merge style). The label must be created in repo Settings → Labels before merging; any color works, but a light-green "approval" color reads well.
  • The workflow uses secrets.STDLIB_BOT_GITHUB_TOKEN for the comment step (same as check_required_files.yml). Should it use STDLIB_BOT_PAT_REPO_WRITE instead for consistency with generate_pr_commit_message.yml?

Other

Any other information relevant to this pull request?

End-to-end verification plan after merge:

  1. Open a draft PR from a non-collaborator account that bumps a SHA in an existing workflow. Confirm the check fails, the error message points at the offending line, and stdlib-bot leaves a single comment with unblocking instructions.
  2. Push a non-SHA edit (comment / run: change) on the same PR. Confirm the check passes.
  3. Apply the Allow Action Pin Changes label. Confirm the scan step is skipped and the check goes green; remove the label and confirm it fails again.
  4. Confirm a Dependabot PR bumping a pinned action passes without intervention.
  5. Confirm an untrusted PR that renames .github/workflows/<file>.yml out of the directory still fails if it touches SHA lines.

Checklist

Please ensure the following tasks are completed before submitting this pull request.

AI Assistance

When authoring the changes proposed in this PR, did you use any kind of AI assistance?

  • Yes
  • No

If you answered "yes" above, how did you use AI assistance?

  • Code generation (e.g., when writing an implementation or fixing a bug)
  • Test/benchmark generation
  • Documentation (including examples)
  • Research and understanding

Disclosure

This PR was authored with Claude Code. The workflow design, regex, trust model, and comment/label bypass were developed interactively with the assistant; I reviewed the final workflow and message copy before opening the PR.


@stdlib-js/reviewers

Adds `.github/workflows/check_workflow_action_pins.yml`, which fails a
pull request when a non-maintainer author adds or removes a pinned
`uses: <action>@<SHA>` line in any file under `.github/workflows/**`.
Trust is determined via `github.event.pull_request.author_association`
(OWNER/MEMBER/COLLABORATOR), with automatic passthrough for dependabot
and stdlib-bot. Maintainers may bypass the check by applying the
`Allow Action Pin Changes` label. On failure, stdlib-bot comments on
the pull request with the offending lines and unblocking instructions.

---
type: pre_commit_static_analysis_report
description: Results of running static analysis checks when committing changes.
report:
  - task: lint_filenames
    status: passed
  - task: lint_editorconfig
    status: passed
  - task: lint_markdown
    status: na
  - task: lint_package_json
    status: na
  - task: lint_repl_help
    status: na
  - task: lint_javascript_src
    status: na
  - task: lint_javascript_cli
    status: na
  - task: lint_javascript_examples
    status: na
  - task: lint_javascript_tests
    status: na
  - task: lint_javascript_benchmarks
    status: na
  - task: lint_python
    status: na
  - task: lint_r
    status: na
  - task: lint_c_src
    status: na
  - task: lint_c_examples
    status: na
  - task: lint_c_benchmarks
    status: na
  - task: lint_c_tests_fixtures
    status: na
  - task: lint_shell
    status: na
  - task: lint_typescript_declarations
    status: passed
  - task: lint_typescript_tests
    status: na
  - task: lint_license_headers
    status: passed
---
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant