Skip to content

ci: pin GitHub Actions to commit SHAs and harden CI workflow (#62) - #100

Merged
skuenzli merged 7 commits into
mainfrom
chore/consolidate-pin-github-actions
Jun 27, 2026
Merged

ci: pin GitHub Actions to commit SHAs and harden CI workflow (#62)#100
skuenzli merged 7 commits into
mainfrom
chore/consolidate-pin-github-actions

Conversation

@skuenzli

Copy link
Copy Markdown
Contributor

Closes #62.

Hardens the CI and release process against supply-chain and related attacks, using .github/workflows/CI.yml as the surface. Applies the practices from the Wiz and Corgea GitHub Actions security guides.

Changes

  • Pin all 7 actions to commit SHAs (with the human-readable tag in a trailing comment); zero bare @vN refs remain. A compromised upstream tag can no longer silently ship code into our build/release.

  • Bump to cooldown-eligible versions (prefer-latest-but-≥7-days, per CLAUDE.md "Version-selection policy"):

    Action Old New Commit SHA
    actions/checkout v4 v7.0.0 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
    actions/setup-python v5 v6.2.0 a309ff8b426b58ec0e2a45f0f869d46889d02405
    actions/upload-artifact v4 v7.0.1 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
    actions/download-artifact v4 v8.0.1 3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
    actions/attest-build-provenance v1 v4.1.0 a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32
    PyO3/maturin-action v1 v1.51.0 e83996d129638aa358a18fbd1dfb82f0b0fb5d3b
    uraimo/run-on-arch-action v2 v3.1.0 f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9

    (setup-python v6.3.0 and attest v4.1.1 exist but were still inside the 7-day cooldown at authoring time.)

  • persist-credentials: false on every checkout step — nothing in CI git-pushes/commits (we publish via OIDC), so the persisted .git/config credential was pure leak surface. The public third_party/cedar-integration-tests submodule clones fine without it.

  • New lint-workflows job runs zizmor (pipx run zizmor==1.25.2, only checkout SHA-pinned) over .github/workflows/ — enforces SHA pinning and flags injection/dangerous-trigger/over-permission regressions on every future PR.

  • Removed sccache: 'true' from the 3 build steps. zizmor flagged cache-poisoning (a cross-run cache feeding the builds that produce published wheels); removing it eliminates the vector at the root rather than suppressing the finding. Trade-off: cold Rust compiles in CI.

  • Rewrote the stale maturin autogen header to a hand-maintained / SHA-pinned warning (regenerating with maturin generate-ci would clobber the pins).

Per-action changelog review

All bumps are SAFE for our GitHub-hosted CI. The Node-24 / runner ≥ v2.327.1 floor that several majors introduce only affects self-hosted runners.

  • checkout v7 — only behavior change blocks fork-PR checkout for pull_request_target/workflow_run (we use neither). submodules handling unchanged.
  • setup-python v6architecture/python-version inputs unchanged; 3.13 resolves on all runners; no required inputs.
  • upload-artifact v7 — default archive: true still zips, preserving download compatibility; distinct artifact names, no collisions.
  • download-artifact v8 — no-input "download all" still creates one wheels-*/ dir per artifact (release-job glob intact); v8 errors on hash-mismatch (non-event for valid artifacts).
  • attest-build-provenance v4⚠️ one behavior change accepted: v2+ emits a single combined attestation over wheels-*/* instead of one-per-wheel. This is the current GitHub-recommended pattern. Required permissions unchanged.
  • maturin-action — no v2 exists; --find-interpreter / manylinux: auto / command: sdist / command: upload (OIDC) unchanged through v1.51.0.
  • run-on-arch v3arch/distro: ubuntu22.04/githubToken/install/run unchanged; v3 is a QEMU 9.2.2 upgrade that improves aarch64 emulation reliability.

Already-aligned (left as-is)

Workflow-level permissions: contents: read with the release job scoping its own id-token/contents:write/attestations; safe triggers (pull_request, not pull_request_target); OIDC Trusted Publishing with no static secrets; pypi-release environment approval gate; no untrusted ${{ github.event.* }} shell interpolation. Deny-by-default permissions: {} was considered and declined (marginal over the existing contents: read).

Validation

  • Local: ruby -ryaml parse OK · zizmor 1.25.2 → exit 0, no findings · actionlint (Docker) → exit 0.
  • This PR's CI exercises checkout, setup-python, maturin-action (build + sdist), upload-artifact, run-on-arch (aarch64 row), and the new zizmor lint job across linux x86_64/aarch64, windows, macos aarch64/x86_64, sdist.
  • Not exercised here: download-artifact@v8, attest-build-provenance@v4, and maturin upload only run in the release job, which is skipped on PRs. Per the agreed plan these are validated on the next real release (no throwaway tag); maturin upload --skip-existing keeps that idempotent, and the publish step runs only after attestation so a failure can't publish a bad artifact. Rollback = revert this PR.

Out of scope

Repo/org settings that can't be expressed in the workflow file (CODEOWNERS on .github/workflows/, disabling Actions PR auto-approve, org action-allowlist + SHA-pin enforcement, re-enabling Dependabot version-updates so the cooldown auto-enforces) are tracked separately for a follow-up decision.

🤖 Generated with Claude Code

skuenzli and others added 7 commits June 27, 2026 07:16
Draft the task plan for consolidating GitHub Actions updates and pinning
them to commit SHAs (GH #62), and record the version-selection policy that
came out of scoping it.

- docs/tasks/62: objective, cooldown-eligible target versions per action,
  and high-level steps (research changelogs -> resolve SHAs -> single-commit
  edit -> validate on PR -> optional release smoke test).
- CLAUDE.md: new "Version-selection policy" subsection — prefer latest but
  >=7 days old (14 for major), matching dependabot.yml cooldown. Notes it is
  a manual discipline today (cargo update / pip-compile have no age gate;
  dependabot version-updates are disabled) and that security updates are
  exempt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve the three open questions from task-clarify:

- Release-job validation: no test tags / no workflow_dispatch dry run.
  Upgrade the release actions in this PR and validate on the next real
  release (maturin --skip-existing keeps it idempotent; failures surface
  before publish; revert to roll back).
- uraimo/run-on-arch-action: adopt v3.1.0 (validated by the aarch64 PR row).
- maturin autogen header: update it to warn the file is hand-maintained and
  SHA-pinned, and must not be regenerated via maturin generate-ci.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Broaden the task from "bump + SHA-pin actions" to hardening the CI and
release process against supply-chain attacks, applying the practices in
the Wiz and Corgea GitHub Actions security references.

- Objective: add a security-posture assessment recording what already
  aligns (least-privilege permissions, safe triggers, OIDC/no static
  secrets, environment approval gate) vs the gaps this task closes.
- Fold in two non-version hardening changes: persist-credentials: false
  on all checkout steps, and a new zizmor lint job that enforces SHA
  pinning on future PRs. Decline deny-by-default permissions: {} (marginal
  over the existing workflow-level contents: read).
- Detailed plan reflowed into Phases A-G (19 steps) with the new work and
  updated local verification; acceptance criteria expanded.
- Out-of-band repo/org recommendations (CODEOWNERS, PR auto-approve, org
  allowlist + SHA-pin enforcement, etc.) tracked in the gitignored
  out/62-out-of-band-hardening.md, not this PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-action changelog review across all 8 actions (7 existing + zizmor).
All bumps SAFE for cedar-py's GitHub-hosted CI; the Node-24/runner floor
several majors add only affects self-hosted runners.

- One behavioral change accepted: attest-build-provenance v2+ emits a
  single combined attestation over wheels-*/* instead of one-per-wheel.
- zizmor approach resolved: pipx run zizmor==<eligible> in a run step
  (only checkout SHA-pinned), declining the official action for minimal
  third-party surface. Plan steps updated to match.
- Confirmed persist-credentials: false is safe (no git push; public
  submodule) and there's no untrusted github.event.* shell interpolation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-checked cooldown (setup-python v6.2.0 / attest v4.1.0 stay; latest
still inside 7-day window) and resolved all 7 target tags to verified
40-hex commit SHAs via gh api repos/<o>/<r>/commits/<tag>. Mapping frozen
in the task doc for the Phase C edit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Harden the CI/release process against supply-chain attacks per the Wiz and
Corgea GitHub Actions security references.

- Pin all 7 actions to commit SHAs with version-tag comments (zero bare
  @vn refs), bumped to cooldown-eligible versions:
  checkout v7.0.0, setup-python v6.2.0, upload-artifact v7.0.1,
  download-artifact v8.0.1, attest-build-provenance v4.1.0,
  maturin-action v1.51.0, run-on-arch-action v3.1.0.
- Add persist-credentials: false to all checkout steps so no git
  credential is persisted in .git/config (we publish via OIDC, never
  git-push). Public submodule clone is unaffected.
- Add a lint-workflows job running zizmor (pipx run, only checkout
  SHA-pinned) to enforce pinning + flag injection/trigger risks on PRs.
- Remove sccache: 'true' from the build steps: zizmor flagged
  cache-poisoning (runtime cache in an artifact-publishing workflow);
  removing it eliminates the vector from the builds that produce
  published wheels rather than suppressing the finding.
- Replace the stale maturin autogen header with a hand-maintained /
  SHA-pinned warning.

attest-build-provenance v2+ now emits a single combined attestation over
wheels-*/* (was one-per-wheel) — accepted as the current recommended
pattern. Release-job actions (download-artifact, attest, maturin upload)
are PR-skipped and validate on the next real release.

Verified locally: yaml parse OK, zizmor 1.25.2 exit 0, actionlint exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Capture the durable guidance from the GH #62 work in CLAUDE.md:

- New "GitHub Actions" subsection: all CI.yml uses: refs are SHA-pinned
  with tag comments and enforced by a zizmor lint job; how to bump an
  action (cooldown-eligible version -> gh api commits/<tag> to resolve
  the SHA); do not regenerate via maturin generate-ci; sccache is
  intentionally absent (cache-poisoning), so don't re-add caching to the
  publish-bound build jobs; why GH_TOKEN is in the lint job; release-job
  actions are PR-skipped.
- Update the #62 follow-on note to reflect PR #100 and point at the
  gitignored out/62-out-of-band-hardening.md for the remaining repo/org
  settings follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@skuenzli
skuenzli merged commit 6f66603 into main Jun 27, 2026
8 checks passed
@skuenzli
skuenzli deleted the chore/consolidate-pin-github-actions branch June 27, 2026 21:23
swenger pushed a commit to swenger/cedar-py that referenced this pull request Jul 10, 2026
Capture the durable guidance from the GH k9securityio#62 work in CLAUDE.md:

- New "GitHub Actions" subsection: all CI.yml uses: refs are SHA-pinned
  with tag comments and enforced by a zizmor lint job; how to bump an
  action (cooldown-eligible version -> gh api commits/<tag> to resolve
  the SHA); do not regenerate via maturin generate-ci; sccache is
  intentionally absent (cache-poisoning), so don't re-add caching to the
  publish-bound build jobs; why GH_TOKEN is in the lint job; release-job
  actions are PR-skipped.
- Update the k9securityio#62 follow-on note to reflect PR k9securityio#100 and point at the
  gitignored out/62-out-of-band-hardening.md for the remaining repo/org
  settings follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

ci: consolidate GitHub Actions updates and pin to commit SHAs

1 participant