Skip to content

ci: pin third-party GitHub Actions to full commit SHAs#3776

Merged
vfdev-5 merged 6 commits into
pytorch:masterfrom
arpitjain099:chore/pin-actions-to-sha
Jun 25, 2026
Merged

ci: pin third-party GitHub Actions to full commit SHAs#3776
vfdev-5 merged 6 commits into
pytorch:masterfrom
arpitjain099:chore/pin-actions-to-sha

Conversation

@arpitjain099

Copy link
Copy Markdown
Contributor

Pin third-party GitHub Actions to full commit SHAs

This follows up on #3754. As @vfdev-5 suggested there (#3754 (comment)), the higher-value hardening for these workflows is pinning the action references rather than touching permissions, so this PR does exactly that and nothing else.

Every external action uses: reference in .github/workflows/ that was on a tag or branch (actions/checkout@v6, astral-sh/setup-uv@v7, codecov/codecov-action@v6, the triage.yml @v* refs, etc.) is now pinned to the full 40-character commit SHA, with the version kept as a trailing comment in the OpenSSF-recommended form:

uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

Why

A tag like @v6 is mutable: the action author (or someone who compromises their account) can force-move it to point at different code at any time, and your CI would pick up that code on the next run with no diff in this repo. That is the mechanism behind the tj-actions/changed-files incident in March 2025, where a moved tag exposed secrets across thousands of repositories. A commit SHA is immutable, so pinning to it removes that class of supply-chain risk while still running the same code the tag points at today.

Details

  • Each SHA was resolved from the exact commit the current tag points to (annotated tags dereferenced to the underlying commit), so there is no behavior or version change here, only an immutable pin.
  • Dependabot keeps working: with the github-actions ecosystem enabled it reads the version from the trailing comment and will continue to open bump PRs (and re-pin to the new SHA), so this does not freeze the actions or add maintenance burden.
  • Local reusable actions (./test-infra/.github/actions/...) are intentionally left unpinned, since they resolve from the checked-out tree, not an external tag.
  • Commented-out example steps were left untouched.
  • No permissions: blocks or any other workflow content were changed; this PR is only SHA pinning.

Happy to adjust the comment style or split this per-file if you would prefer smaller diffs.

Pin every external action reference in .github/workflows to its full
40-character commit SHA, keeping the human-readable version as a trailing
comment (the OpenSSF-recommended form):

  uses: actions/checkout@<sha> # v6

A mutable tag like @v6 can be force-moved to point at different code after
review, so a compromised or hijacked action tag can ship arbitrary code into
CI. A pinned commit SHA is immutable and removes that class of risk. Each SHA
was resolved from the exact commit the current tag points to, so behavior is
unchanged. Dependabot continues to update these because the version label is
preserved in the trailing comment.

Local reusable action references (./test-infra/...) and commented-out steps
are left as-is. No permissions or other workflow changes are included.

Signed-off-by: arpitjain099 <arpitjain099@gmail.com>
@vfdev-5

vfdev-5 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

@arpitjain099 how did you produced all the sha values?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the repository’s GitHub Actions workflows by replacing mutable third-party uses: references (tags/branches) with immutable 40-character commit SHA pins, keeping the prior version as a trailing comment for auditability and automated updates.

Changes:

  • Pinned actions/checkout, astral-sh/setup-uv, codecov/codecov-action, nick-fields/retry, and other third-party actions to full commit SHAs across CI/test/release workflows.
  • Pinned automation workflows (triage + Discord notifications) action references to SHAs.
  • Pinned release and Docker workflows’ third-party action references to SHAs.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated no comments.

Show a summary per file
File Description
.github/workflows/unit-tests.yml Pins checkout/uv/retry/codecov and MNIST download action to SHAs for unit-test CI hardening.
.github/workflows/typing-checks.yml Pins checkout/uv to SHAs for typing workflow supply-chain hardening.
.github/workflows/triage.yml Pins checkout and labeler actions to SHAs for triage workflow hardening.
.github/workflows/tpu-tests.yml Pins checkout/setup-python/cache/retry/codecov and MNIST download action to SHAs.
.github/workflows/stable-release-pypi.yml Pins checkout and setup-uv to SHAs for PyPI release workflow hardening.
.github/workflows/stable-release-anaconda.yml Pins checkout and setup-miniconda to SHAs for conda release workflow hardening.
.github/workflows/pytorch-version-tests.yml Pins checkout/uv/retry and MNIST download action to SHAs for scheduled version-test workflow hardening.
.github/workflows/mps-tests.yml Pins checkout/codecov and MNIST download action to SHAs for macOS MPS workflow hardening.
.github/workflows/hvd-tests.yml Pins checkout/uv/retry/codecov and MNIST download action to SHAs for Horovod workflow hardening.
.github/workflows/gpu-tests.yml Pins checkout/retry/codecov to SHAs for GPU workflow hardening.
.github/workflows/gpu-hvd-tests.yml Pins checkout/codecov to SHAs for GPU+Horovod workflow hardening.
.github/workflows/docs.yml Pins checkout/uv/retry and gh-pages deploy action to SHAs for docs workflows hardening.
.github/workflows/docker-build.yml Pins checkout and changed-files action to SHAs for Docker workflow hardening (local reusable actions unchanged).
.github/workflows/discord_pull_requests.yaml Pins discuss-on-discord action to a SHA for Discord PR notifications hardening.
.github/workflows/discord_issues.yml Pins discuss-on-discord action to a SHA for Discord issue notifications hardening.
.github/workflows/code-style-checks.yml Pins checkout/uv to SHAs for style-check workflow hardening.
.github/workflows/binaries-nightly-release.yml Pins checkout/setup-miniconda/create-an-issue actions to SHAs for nightly release workflow hardening.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@arpitjain099

arpitjain099 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

@vfdev-5 Resolved each one against the tag the workflow already uses, via the GitHub API: gh api repos/<owner>/<action>/git/ref/tags/<tag> and followed annotated tags down to the underlying commit (a couple were doubly-nested, e.g. actions/first-interaction's v1 -> v1.3.0 -> commit). So every # v6-style comment is the commit that tag points to right now, not a downgrade. Dependabot still bumps them fine since the version stays in the trailing comment. Can share the full tag -> sha list if it helps the review.

@vfdev-5 vfdev-5 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arpitjain099 you can annotate all the sha like in the comments to ensure that we point out to correct tags, this would help with the review. Thanks!

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actions/checkout@df4cb1c

https://github.com/actions/checkout/commit/df4cb1c069e1874edd31b4311f1884172cec0e10


- name: Setup Miniconda
uses: conda-incubator/setup-miniconda@v4
uses: conda-incubator/setup-miniconda@8ee1f361103df19b6f8c8655fd3967a8ecb162d5 # v4

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arpitjain099

Copy link
Copy Markdown
Contributor Author

Thanks @vfdev-5. Each ref already has the tag it was resolved from as a trailing comment, e.g. actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6, and the SHA is what that tag points to right now.

If it helps the review, I'm happy to change those comments to the exact release tag instead of the floating major, e.g. # v6.0.1 rather than # v6, since a reader can then check actions/checkout@v6.0.1 resolves to that exact SHA. Just let me know if you'd like that and I'll update them.

Update the trailing comment on each SHA-pinned third-party action from the
floating major tag (e.g. # v6) to the exact immutable release tag whose
commit equals that SHA (e.g. # v6.0.3). This lets reviewers verify each pin
maps to a real release by checking that <action>@<tag> resolves to the
pinned commit. Verified every SHA against the upstream tag list via the
GitHub tags API. pytorch-ignite/download-mnist-github-action stays # master
since that repo has no release tags and is pinned to the master branch tip.

Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
@arpitjain099

Copy link
Copy Markdown
Contributor Author

Done, thanks for the nudge. Updated every pin to the exact release tag its SHA resolves to (e.g. actions/checkout is now # v6.0.3 rather than the floating # v6), so each action@tag can be checked against the SHA directly. The diff is comment-only, no SHAs or refs changed.

One to flag: pytorch-ignite/download-mnist-github-action has no release tags, so that pin is the current tip of its master branch (left as # master). It's our own action so the risk is low, but it does track a branch rather than an immutable release. Happy to handle that one differently if you'd prefer.

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arpitjain099 how can I check easily that this sha points to v6.0.3 tag?
There are tens of sha hashes, how can we ensure that they are not already pointing to some incorrect release?

@arpitjain099

Copy link
Copy Markdown
Contributor Author

Good question - here's the check, and I re-verified every pin in this PR.

To verify any single pin, resolve its tag to a commit:

git ls-remote https://github.com/actions/checkout refs/tags/v6.0.3 'refs/tags/v6.0.3^{}'

The ^{} line dereferences the (annotated) tag to the commit it points at - that commit is what's pinned, and the # v6.0.3 comment records which tag it came from.

I re-ran that for all of them and every SHA matches its tag: actions/checkout v6.0.3, actions/setup-python v6.2.0, actions/labeler v6.1.0, actions/cache v5.0.5, astral-sh/setup-uv v7.6.0, codecov/codecov-action v6.0.1, conda-incubator/setup-miniconda v4.0.1, nick-fields/retry v4.0.0, peaceiris/actions-gh-pages v4.1.0, JasonEtco/create-an-issue v2.9.2, EndBug/discuss-on-discord v1.1.0, umani/changed-files v4.2.0.

One note: pytorch-ignite/download-mnist-github-action has no release tags, so it's pinned to the current master commit (622fc8c) with a # master comment - the right posture for a tagless action, since a SHA can't drift the way @master would.

For ongoing assurance rather than checking by hand, the usual setup is a tool that verifies/updates pins automatically: pinact, ratchet, or StepSecurity's pin-github-action; zizmor can also flag unpinned ones in CI. Dependabot understands the # vX.Y.Z comments and bumps the SHA and comment together. Happy to wire up a CI check or a short CONTRIBUTING note if you'd like the repo to enforce it going forward.

@TahaZahid05

Copy link
Copy Markdown
Collaborator

@vfdev-5 Code I used to verify shas:

import subprocess
import sys

ACTIONS = [
    ("actions/checkout", "v6.0.3", "df4cb1c069e1874edd31b4311f1884172cec0e10"),
    ("conda-incubator/setup-miniconda", "v4.0.1", "8ee1f361103df19b6f8c8655fd3967a8ecb162d5"),
    ("JasonEtco/create-an-issue", "v2.9.2", "1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5"),
    ("astral-sh/setup-uv", "v7.6.0", "37802adc94f370d6bfd71619e3f0bf239e1f3b78"),
    ("EndBug/discuss-on-discord", "v1.1.0", "bff2908cbd855fff72c1007aed386e09144727e1"),
    ("umani/changed-files", "v4.2.0", "138acc60bcaa548e0c194fc69ed36321ee8466d2"),
    ("peaceiris/actions-gh-pages", "v4.1.0", "84c30a85c19949d7eee79c4ff27748b70285e453"),
    ("nick-fields/retry", "v4.0.0", "ad984534de44a9489a53aefd81eb77f87c70dc60"),
    ("codecov/codecov-action", "v6.0.1", "e79a6962e0d4c0c17b229090214935d2e33f8354"),
    ("actions/setup-python", "v6.2.0", "a309ff8b426b58ec0e2a45f0f869d46889d02405"),
    ("actions/cache", "v5.0.5", "27d5ce7f107fe9357f9df03efb73ab90386fccae"),
    ("actions/labeler", "v6.1.0", "f27b608878404679385c85cfa523b85ccb86e213"),
    ("pytorch-ignite/download-mnist-github-action", "master", "622fc8c4ff50b24322819f54f48624f26932892b"),
]


def verify_action(repo, ref, pinned_sha):
    url = f"https://github.com/{repo}"

    if ref == "master":
        ref_path = "refs/heads/master"
        ref_deref = None
    else:
        ref_path = f"refs/tags/{ref}"
        ref_deref = f"refs/tags/{ref}^{{}}"

    cmd = ["git", "ls-remote", url, ref_path]
    if ref_deref:
        cmd.append(ref_deref)

    output = subprocess.check_output(cmd).decode("utf-8").strip()

    lines = [line.split() for line in output.split("\n") if line.strip()]

    resolved_sha = None
    if len(lines) == 2:
        resolved_sha = lines[1][0]
    elif len(lines) == 1:
        resolved_sha = lines[0][0]

    if not resolved_sha:
        return "ERROR", f"Could not resolve reference {ref}"

    if resolved_sha == pinned_sha:
        return "SAFE", resolved_sha
    else:
        return "MISMATCH", f"Remote is {resolved_sha}, PR has {pinned_sha}"


def main():
    print(f"{'ACTION':<45} | {'TAG/BRANCH':<12} | {'STATUS':<8} | {'DETAILS'}")
    print("-" * 110)
    failed = False

    for repo, ref, sha in ACTIONS:
        status, detail = verify_action(repo, ref, sha)
        print(f"{repo:<45} | {ref:<12} | {status:<8} | {detail}")
        if status != "SAFE":
            failed = True

    if failed:
        sys.exit(1)
    else:
        print("\nAll queried action SHAs have been successfully verified as safe!")


if __name__ == "__main__":
    main()

@vfdev-5 vfdev-5 enabled auto-merge June 25, 2026 14:43
@vfdev-5 vfdev-5 added this pull request to the merge queue Jun 25, 2026
Merged via the queue into pytorch:master with commit 2f36de5 Jun 25, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants