Skip to content

ci: supply-chain hardening (PR 2/2) — SBOM + build-provenance attestation for releases#43

Merged
Fieldnote-Echo merged 4 commits into
mainfrom
ci/release-sbom-provenance
May 25, 2026
Merged

ci: supply-chain hardening (PR 2/2) — SBOM + build-provenance attestation for releases#43
Fieldnote-Echo merged 4 commits into
mainfrom
ci/release-sbom-provenance

Conversation

@Fieldnote-Echo
Copy link
Copy Markdown
Owner

Part 2 of 2 of the pre-release supply-chain hardening (PR-1 = #42, the always-on CI). This adds provenance to the held release workflows (both workflow_dispatch-only — merging this triggers nothing). Built by 2 Opus subagents on disjoint files, centrally verified.

release-crate.yml (crates.io)

  • harden-runner (egress audit) on all 3 jobs.
  • CycloneDX SBOM (cargo cyclonedx --manifest-path Cargo.toml) uploaded as a build artifact (crates.io doesnt host SBOMs).
  • actions/attest-build-provenance@v4.1.0 SLSA provenance for the packaged .crate, run before cargo publish (fail-closed: no attestation → no publish). attestations: write added to the publish job.
  • OIDC auth, cargo publish, and the verify / require-ci-green gates are unchanged.

release-python.yml (PyPI)

  • harden-runner on all 4 jobs.
  • CycloneDX SBOM for the binding (the wheel is the compiled Rust ext) generated once in build-sdist, uploaded as an artifact.
  • attest-build-provenance@v4.1.0 GitHub SLSA provenance for the wheels + sdist, run before the pypa publish (fail-closed) — complementary to the PyPI-side PEP 740 attestations the pypa/gh-action-pypi-publish step already emits. attestations: write added to the publish job.
  • The wheel matrix, maturin, pytest, require-ci-green, and the pypa publish are unchanged.

Verified

  • YAML valid; harden-runner on all 7 jobs (3 + 4); attest-build-provenance SHA-pinned in both; attestations: write on both publish jobs; every added action SHA-pinned.
  • The subagents ran cargo-cyclonedx locally and caught that it rejects -p (it scopes via --manifest-path) — so the encoded command is the verified one, not a guess.
  • The OIDC/publish/gate logic in both pipelines is byte-for-byte unchanged except for the additive steps.

Publish remains HELD on your explicit go. Merges under the strict protection (CI + 1 non-self code-owner approval).

Together with #42 this closes the pre-release supply-chain asks: SBOM ✓, hardened runner ✓, release attestation ✓, Dependabot (7-day cooldown) ✓, CodeQL ✓.

Publish stays HELD (workflow_dispatch). Adds: step-security/harden-runner@v2.19.4 (egress audit) on all 3 jobs; a CycloneDX SBOM (cargo-cyclonedx --manifest-path Cargo.toml) uploaded as a build artifact (crates.io doesn't host SBOMs); and actions/attest-build-provenance@v4.1.0 SLSA provenance for the packaged .crate, run BEFORE cargo publish so a failed attestation fails the release closed. attestations: write added to the publish job. OIDC auth, cargo publish, and the verify/require-ci-green gates are unchanged. All actions SHA-pinned.
Publish stays HELD. Adds: harden-runner@v2.19.4 (egress audit) on all 4 jobs; a CycloneDX SBOM (cargo-cyclonedx on ordvec-python/Cargo.toml — the wheel is the compiled Rust ext) uploaded from build-sdist; and actions/attest-build-provenance@v4.1.0 GitHub SLSA provenance for the wheels + sdist, run BEFORE the pypa publish (fail-closed) — complementary to the PyPI-side PEP 740 attestations the pypa action already emits. attestations: write added to the publish job. The wheel matrix, maturin, pytest, require-ci-green, and pypa publish are unchanged. All actions SHA-pinned.
@gemini-code-assist
Copy link
Copy Markdown

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Supply-chain hardening: SBOM + build provenance attestation for releases

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add supply-chain hardening with SBOM generation and build provenance attestation
• Integrate harden-runner for egress audit on all release jobs
• Generate CycloneDX SBOMs for crate and Python binding packages
• Attest build provenance using GitHub SLSA before publishing
• Add attestations write permission to publish jobs
Diagram
flowchart LR
  A["Release Workflows"] --> B["Harden Runner<br/>Egress Audit"]
  B --> C["Generate SBOM<br/>CycloneDX"]
  C --> D["Attest Build<br/>Provenance SLSA"]
  D --> E["Publish to Registry<br/>Fail-Closed"]
  F["Upload SBOM<br/>Artifact"] -.-> E

Loading

File Changes

1. .github/workflows/release-crate.yml ✨ Enhancement +44/-0

Crate release hardening with SBOM and provenance

• Add harden-runner@v2.19.4 with egress audit policy to all 3 jobs (verify, require-ci-green,
 publish)
• Add cargo package step to generate the .crate artifact before attestation
• Generate CycloneDX SBOM using cargo-cyclonedx and upload as build artifact
• Add attest-build-provenance@v4.1.0 step before cargo publish to create SLSA provenance
• Add attestations write permission to publish job

.github/workflows/release-crate.yml


2. .github/workflows/release-python.yml ✨ Enhancement +44/-0

Python release hardening with SBOM and provenance

• Add harden-runner@v2.19.4 with egress audit policy to all 4 jobs (build-wheels, build-sdist,
 require-ci-green, publish)
• Generate CycloneDX SBOM for Python binding crate in build-sdist job and upload as artifact
• Add attest-build-provenance@v4.1.0 step before PyPI publish to create SLSA provenance for wheels
 and sdist
• Add attestations write permission to publish job
• Complementary to existing PyPI PEP 740 attestations from pypa action

.github/workflows/release-python.yml


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 25, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Unpinned SBOM tool version ✓ Resolved 🐞 Bug ⛨ Security
Description
Both release workflows install cargo-cyclonedx without pinning a version, so future tool releases
(or a compromised crates.io release) can change SBOM output or execute unexpected code in the
release pipeline. --locked does not prevent the installed tool version from drifting over time.
Code

.github/workflows/release-crate.yml[R126-128]

Evidence
The release-crate workflow installs cargo-cyclonedx from crates.io at runtime without a version
pin, and release-python does the same; this makes the release pipeline dependent on whatever version
is current at execution time.

.github/workflows/release-crate.yml[121-129]
.github/workflows/release-python.yml[124-129]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The workflows run `cargo install cargo-cyclonedx --locked` without `--version`, which installs whatever the latest crates.io version is at runtime. This undermines release reproducibility and increases supply-chain risk for a security-sensitive release workflow.

### Issue Context
`--locked` only enforces the tool's own dependency lockfile, not the version of `cargo-cyclonedx` being installed.

### Fix Focus Areas
- .github/workflows/release-crate.yml[121-129]
- .github/workflows/release-python.yml[124-129]

### Suggested fix
- Pin the tool version explicitly, e.g.:
 - `cargo install cargo-cyclonedx --version <KNOWN_GOOD_VERSION> --locked`
- (Optional hardening) Consider installing from a pinned source (e.g., a specific git rev) if you want stronger integrity guarantees than crates.io latest.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. SBOM mixed into dist ✓ Resolved 🐞 Bug ☼ Reliability
Description
release-python.yml uploads an SBOM artifact and the publish job downloads all artifacts into
dist/ with merge-multiple: true, so the publish directory now contains both distributions and
non-distribution SBOM JSON. This makes the publish directory contents brittle for any step that
assumes dist/ contains only wheels/sdists (including future changes to publish globs).
Code

.github/workflows/release-python.yml[R118-134]

Evidence
The workflow adds a new sbom-python artifact from build-sdist, and the publish job downloads (and
merges) all artifacts into a single dist/ directory, which will therefore include the SBOM
alongside the distributions.

.github/workflows/release-python.yml[118-135]
.github/workflows/release-python.yml[185-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The build-sdist job now produces an SBOM artifact, and the publish job downloads all artifacts into `dist/` with merge enabled. This mixes SBOM JSON with the actual distribution files in a directory implicitly treated as the publish payload.

### Issue Context
The publish job uses a single `actions/download-artifact` step without an `name:` filter and merges artifacts into `dist/`.

### Fix Focus Areas
- .github/workflows/release-python.yml[118-135]
- .github/workflows/release-python.yml[185-203]

### Suggested fix
Pick one:
- Download only wheel/sdist artifacts into `dist/` (use `name:` filtering, or separate download steps), and download `sbom-python` into a different directory.
- Alternatively, keep downloading everything but configure the publish step explicitly to only upload `dist/*.whl` and `dist/*.tar.gz` (and keep non-distributions elsewhere).
- Add `if-no-files-found: error` to the SBOM upload step if you want SBOM generation to be fail-closed like the crate workflow.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Credential minted too early ✓ Resolved 🐞 Bug ⛨ Security
Description
In release-crate.yml, the crates.io OIDC credential is minted before SBOM/provenance steps that
don't need it, increasing the time a valid publish credential exists during additional tool
installation/execution. This weakens defense-in-depth in a job that already has id-token: write
and attestations: write permissions.
Code

.github/workflows/release-crate.yml[R107-133]

Evidence
The workflow currently mints the crates.io credential and then runs the newly added
packaging/SBOM/attestation steps before the actual publish step consumes the token.

.github/workflows/release-crate.yml[107-141]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The publish credential is minted earlier than necessary, before running extra steps (including installing and executing additional tooling). Even if the token is short-lived, minimizing credential lifetime is a standard release-pipeline hardening measure.

### Issue Context
The new SBOM/provenance steps were inserted after the `crates-io-auth-action` step, so the token is minted and then several steps run before `cargo publish` uses it.

### Fix Focus Areas
- .github/workflows/release-crate.yml[107-142]

### Suggested fix
- Reorder steps so SBOM generation + provenance attestation run first.
- Run `rust-lang/crates-io-auth-action` immediately before `cargo publish` (the first step that needs the crates.io token).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread .github/workflows/release-crate.yml
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds supply-chain hardening to the repository’s manual (workflow_dispatch-only) release workflows by generating CycloneDX SBOMs and producing GitHub SLSA build-provenance attestations prior to publishing.

Changes:

  • Add step-security/harden-runner (egress audit mode) to all jobs in both release workflows.
  • Generate and upload CycloneDX SBOM artifacts for the Rust crate release and the Python binding release.
  • Add actions/attest-build-provenance steps (and attestations: write permission) so provenance is emitted before publishing.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
.github/workflows/release-python.yml Adds runner hardening, a binding-crate SBOM artifact, and SLSA provenance attestation for wheels/sdist before PyPI publish.
.github/workflows/release-crate.yml Adds runner hardening, a crate SBOM artifact, and SLSA provenance attestation for the packaged .crate before crates.io publish.

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

Comment thread .github/workflows/release-python.yml
Comment thread .github/workflows/release-python.yml
Comment thread .github/workflows/release-crate.yml
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9bbd46c0f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release-crate.yml Outdated
Comment thread .github/workflows/release-python.yml
qodo/copilot/Codex(P1): the SBOM tool was installed unpinned — 'cargo install cargo-cyclonedx --locked' selects whatever is latest at runtime (--locked only pins the tool's own deps), which is non-reproducible and a supply-chain risk in a release pipeline. Pin to --version 0.5.9 (the version verified locally) in both release workflows. copilot/Codex(P2): the SBOM upload steps lacked 'if-no-files-found: error', so a missing/failed SBOM could publish silently — add it to both (and collapse a duplicate on the crate upload that the count check caught).
…iew)

qodo: the short-lived crates.io token was minted right after checkout, then sat live through cargo package + the 'cargo install cargo-cyclonedx' SBOM build (third-party code) + attestation before cargo publish used it — a needlessly wide exposure window. Move the crates-io-auth-action step to immediately before cargo publish so the token is minted last. The id:auth -> steps.auth.outputs.token reference is unchanged (auth still runs before publish).
@Fieldnote-Echo
Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 25, 2026

Persistent review updated to latest commit 4060b87

@Fieldnote-Echo Fieldnote-Echo merged commit 6b4d553 into main May 25, 2026
11 checks passed
@Fieldnote-Echo Fieldnote-Echo deleted the ci/release-sbom-provenance branch May 25, 2026 15:42
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