ci: pin GitHub Actions to commit SHAs and harden CI workflow (#62) - #100
Merged
Conversation
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>
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>
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.
Closes #62.
Hardens the CI and release process against supply-chain and related attacks, using
.github/workflows/CI.ymlas 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
@vNrefs 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"):
actions/checkout9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0actions/setup-pythona309ff8b426b58ec0e2a45f0f869d46889d02405actions/upload-artifact043fb46d1a93c77aae656e7c1c64a875d1fc6a0aactions/download-artifact3e5f45b2cfb9172054b4087a40e8e0b5a5461e7cactions/attest-build-provenancea2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32PyO3/maturin-actione83996d129638aa358a18fbd1dfb82f0b0fb5d3buraimo/run-on-arch-actionf9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9(
setup-pythonv6.3.0 andattestv4.1.1 exist but were still inside the 7-day cooldown at authoring time.)persist-credentials: falseon everycheckoutstep — nothing in CI git-pushes/commits (we publish via OIDC), so the persisted.git/configcredential was pure leak surface. The publicthird_party/cedar-integration-testssubmodule clones fine without it.New
lint-workflowsjob runs zizmor (pipx run zizmor==1.25.2, onlycheckoutSHA-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 flaggedcache-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-ciwould 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.
pull_request_target/workflow_run(we use neither).submoduleshandling unchanged.architecture/python-versioninputs unchanged; 3.13 resolves on all runners; no required inputs.archive: truestill zips, preserving download compatibility; distinct artifact names, no collisions.wheels-*/dir per artifact (release-job glob intact); v8 errors on hash-mismatch (non-event for valid artifacts).wheels-*/*instead of one-per-wheel. This is the current GitHub-recommended pattern. Required permissions unchanged.--find-interpreter/manylinux: auto/command: sdist/command: upload(OIDC) unchanged through v1.51.0.arch/distro: ubuntu22.04/githubToken/install/rununchanged; v3 is a QEMU 9.2.2 upgrade that improves aarch64 emulation reliability.Already-aligned (left as-is)
Workflow-level
permissions: contents: readwith the release job scoping its ownid-token/contents:write/attestations; safe triggers (pull_request, notpull_request_target); OIDC Trusted Publishing with no static secrets;pypi-releaseenvironment approval gate; no untrusted${{ github.event.* }}shell interpolation. Deny-by-defaultpermissions: {}was considered and declined (marginal over the existingcontents: read).Validation
ruby -ryamlparse OK · zizmor 1.25.2 → exit 0, no findings · actionlint (Docker) → exit 0.download-artifact@v8,attest-build-provenance@v4, andmaturin uploadonly run in thereleasejob, which is skipped on PRs. Per the agreed plan these are validated on the next real release (no throwaway tag);maturin upload --skip-existingkeeps 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