Skip to content

Pin GitHub Actions to SHAs and add step-security/harden-runner#1588

Closed
wallies wants to merge 1 commit into
Qiskit:mainfrom
wallies:harden-ci
Closed

Pin GitHub Actions to SHAs and add step-security/harden-runner#1588
wallies wants to merge 1 commit into
Qiskit:mainfrom
wallies:harden-ci

Conversation

@wallies
Copy link
Copy Markdown
Contributor

@wallies wallies commented May 20, 2026

Summary

Hardens the GitHub Actions CI by:

  • Replacing every action @v*/@stable/@master/@release/v1 floating ref with a verified SHA pin plus a version comment (catches tag-rewrite supply-chain attacks).
  • Adding step-security/harden-runner@<sha> (audit mode) as the first step of every job in every workflow (14 jobs across main.yml, wheels.yml, docs_dev.yml, docs_release.yml).
  • Adding disable-sudo: true to the 10 jobs that do not need sudo. The 4 jobs that install graphviz/pandoc via sudo apt-get keep the default.
  • Pinning dtolnay/rust-toolchain@stable and @master references to Rust 1.94.1. The existing @1.92 pin in build_lint (carrying a TODO: unpin clippy note) is preserved at 1.92, only its SHA is now pinned.
  • Extending .github/dependabot.yml so the new SHA pins do not go stale: adds the github-actions ecosystem, switches both ecosystems to weekly with a 7-day cooldown, and groups minor/patch updates. Majors continue to open as individual PRs for review.

Every SHA in the diff was verified to resolve to a real commit in its source repo via gh api repos/<owner>/<repo>/commits/<sha>.

Why this matters

In March 2025, two consecutive supply chain attacks struck widely-used GitHub Actions. reviewdog/action-setup@v1 was compromised on March 11, 2025; secrets exfiltrated from that incident were then used a few days later to compromise tj-actions/changed-files on March 14, 2025. In both cases the attacker rewrote existing version tags, so any workflow consuming floating refs like @v1 or @v45 silently picked up the malicious code on its next run and leaked CI/CD secrets. Workflows pinned to full commit SHAs were unaffected.

That attack pattern is the specific class of risk this PR removes. SHA pins make the action ref immutable from the consumer side - retagging at the source no longer changes what runs here. step-security/harden-runner provides defense in depth on top of that: even if a pinned dependency were itself published with malicious code, audit mode logs the unexpected egress and block mode prevents secrets from leaving the runner.

This change brings the repo in line with:

  • OpenSSF Scorecard's Pinned-Dependencies check, which requires dependencies be set to a specific hash rather than a mutable version range.
  • GitHub's own security hardening guide for Actions, which states that "pinning an action to a full-length commit SHA is currently the only way to use an action as an immutable release."

Out of scope

Major version bumps

The pinned actions are intentionally kept at their current major. Several are significantly behind latest:

Action Pinned in this PR Latest Behind by
actions/checkout v4.3.1 v6.0.2 2 majors
actions/setup-python v5.6.0 v6.2.0 1 major
actions/download-artifact v4.3.0 v8.0.1 4 majors
actions/upload-artifact v4.6.2 v7.0.1 3 majors
actions/upload-pages-artifact v3.0.1 v5.0.0 2 majors
docker/setup-qemu-action v2.2.0 v4.0.0 2 majors

These will arrive as individual Dependabot PRs (one per major bump) once this lands, so each can be reviewed on its own merits without bundling breakage risk into the hardening change. The upload-artifact / download-artifact v4 → v5+ transition in particular changed artifact merge semantics and needs careful validation against wheels.yml.

Pre-existing bugs noted but not fixed

actionlint flagged two pre-existing references to ${{ matrix.python-version }} in non-matrix jobs (coverage, docs in main.yml). These render as empty strings in step names today. Out of scope for this PR.

Follow-up: install the Step Security GitHub App and move audit → block

harden-runner is currently in egress-policy: audit, which logs outbound traffic without blocking. To get full value, the maintainers need to:

  1. Install the StepSecurity app at https://github.com/apps/stepsecurity for the Qiskit organization. This gives access to the dashboard at https://app.stepsecurity.io where audit logs are aggregated and a recommended allowlist is generated.

  2. Let several CI runs complete post-merge (a release tag run is especially useful, since wheels.yml only fires on tags) to populate a complete egress profile.

  3. Review the audit dashboard and copy the suggested allowed-endpoints list for each workflow.

  4. Flip egress-policy: auditegress-policy: block in the workflows, supplying the endpoint allowlist:

    - name: Harden the runner
      uses: step-security/harden-runner@<same-sha>
      with:
        egress-policy: block
        allowed-endpoints: >
          api.github.com:443
          github.com:443
          pypi.org:443
          files.pythonhosted.org:443
          static.crates.io:443
          # ...etc from dashboard
  5. Consider extending disable-sudo: true to tests, docs, and the two doc-deploy jobs by moving the graphviz/pandoc install onto a pre-built image or pre-installed runner image, eliminating the last 4 sudo callouts.

Without step 1, harden-runner still works (audit logs land in the job summary), but the maintainer experience for tightening the policy is significantly worse.

Test plan

  • actions/checkout actions/setup-python actions/upload-artifact actions/download-artifact actions/upload-pages-artifact SHAs resolve to expected commits (verified locally; CI re-verifies on first run).
  • main.yml jobs (build_lint, tests, tests_stubs, coverage, docs) succeed on this branch.
  • docs_dev.yml succeeds on a push to main (post-merge).
  • wheels.yml succeeds end-to-end on the next release tag, including disable-sudo: true on builder jobs.
  • Dependabot opens a github-actions PR within 7 days of next eligible upstream release.

References

Replace floating action refs (@v*, @stable, @master, @release/v1) with
verified commit SHAs to mitigate tag-rewrite supply chain attacks of the
class that hit reviewdog/action-setup and tj-actions/changed-files in
March 2025. Add step-security/harden-runner as the first step of every
job in audit mode, with disable-sudo on the 10 jobs that do not need it.
Extend dependabot.yml with a github-actions ecosystem so the new pins
stay current; switch both ecosystems to weekly with a 7-day cooldown and
group minor/patch updates.
Copy link
Copy Markdown
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

Hardening actions by using SHAs is a good idea.

An unsupervised LLM PR attempting to introduce an external dependency on some random AI tooling on every commit and every workflow is the definition of a supply-chain attack itself.

I am closing this, and do not attempt to open a PR like this again. Any further attempt will be treated as actively malicious.

Comment on lines +15 to +18
- name: Harden the runner
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
with:
egress-policy: audit
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is an AI bot that is being proposed to run on every commit and every workflow.

This is exactly what a supply-chain attack looks like.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@jakelishman are you serious? Do you know what step security is and who uses it? Closing this shows a lack of understanding of the community and good practices

@coveralls
Copy link
Copy Markdown

Coverage Report for CI Build 26158142145

Coverage remained the same at 94.722%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • 3 coverage regressions across 1 file.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

3 previously-covered lines in 1 file lost coverage.

File Lines Losing Coverage Coverage
rustworkx-core/src/generators/random_graph.rs 3 87.12%

Coverage Stats

Coverage Status
Relevant Lines: 20141
Covered Lines: 19078
Line Coverage: 94.72%
Coverage Strength: 921944.14 hits per line

💛 - Coveralls

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.

3 participants