Skip to content

ci: fix silently-broken SBOM attestation in release workflow#201

Merged
27Bslash6 merged 2 commits into
mainfrom
fix/release-sbom-attestation
Jun 21, 2026
Merged

ci: fix silently-broken SBOM attestation in release workflow#201
27Bslash6 merged 2 commits into
mainfrom
fix/release-sbom-attestation

Conversation

@27Bslash6

@27Bslash6 27Bslash6 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Problem

The release workflow's attest job has been reporting ✓ green on every release while producing no SBOM attestation at all. Discovered chasing the v0.11.0 release (run 27888587683): the job is green but its annotations show ENOENT: sbom.cdx.json plus three exit code 1/2 failures — all swallowed by continue-on-error: true on every SBOM step.

Note

0.11.0 itself is fine. It published correctly and has a valid build-provenance attestation (that step runs before checkout and was never continue-on-error). PyPI publish and the weekly attestation-check.yml both verify provenance only, which is exactly why the missing SBOM went unnoticed. This is fix-forward — 0.11.0 is not being backfilled.

Root cause (two bugs)

  1. cyclonedx-py requirements had no input file. This is a uv project (uv.lock, no requirements.txt), so the requirements subcommand exits non-zero → python-sbom.cdx.json is never created → the merge step's cp fallback fails → sbom.cdx.json never exists → attest-sbom hits ENOENT.
  2. actions/checkout destroys dist/. The job log shows Deleting the contents of '.../cachekit-py' — checkout wipes the whole workspace (including the downloaded wheels/sdist) before cloning. So the post-checkout attest-sbom step's subject-path: dist/* would have found nothing even if the SBOM had been generated.

Every step's continue-on-error: true hid both failures behind a green check (violates the "no silent failures" rule).

Fix

  • Replace the cyclonedx-py + cargo-sbom + cyclonedx-cli merge pipeline with one anchore/sbom-action (syft) pass that catalogs both uv.lock (Python) and Cargo.lock (Rust). Verified syft registers newUvLockParser for **/uv.lock and parses Cargo.lock statically — it never executes Cargo/build.rs/proc-macros to enumerate deps, unlike cargo-sbom. For a supply-chain security product, generating the SBOM without executing untrusted build code is the correct posture.
  • Check the source into ./source so actions/checkout only wipes that subdir, leaving dist/ at the workspace root for attestation.
  • Drop continue-on-error so a missing SBOM gates publish (publish needs: attest) instead of shipping silently. Releases remain re-runnable via workflow_dispatch.

Net −11 lines (fewer moving parts than before). actionlint clean.

Type

ci: — workflow config only. The published library is byte-identical; this must not cut a release.

Verification

  • actionlint .github/workflows/release-please.yml → clean
  • syft uv.lock + Cargo.lock cataloger support confirmed in source
  • checkout-wipes-dist/ reproduced in the real run log
  • End-to-end proof comes on the next release (or a workflow_dispatch dry run) — by design the SBOM path only runs on release.

Summary by CodeRabbit

  • Chores
    • Streamlined SBOM generation in the release workflow by switching to a dedicated action and producing a single combined Python+Rust CycloneDX SBOM artifact.
    • Strengthened the release attestation flow so SBOM attestation failures are no longer ignored, aligning them with provenance attestation behavior.

The attest job's SBOM steps all carried continue-on-error, so the job
reported success while producing no SBOM. Root cause: `cyclonedx-py
requirements` was invoked with no input file (this is a uv project, no
requirements.txt), so python-sbom.cdx.json was never created, the merge
`cp` fallback failed, and attest-sbom hit ENOENT. Second latent bug:
actions/checkout deletes the workspace (including the downloaded dist/),
so the post-checkout attest-sbom subject-path would have found nothing
even if the SBOM had been generated.

Replace the cyclonedx-py + cargo-sbom + cyclonedx-cli merge pipeline with
a single anchore/sbom-action (syft) pass that catalogs both uv.lock and
Cargo.lock statically. syft never executes Cargo/build code to enumerate
packages, which is the correct posture for a supply-chain security
product. Check source into ./source so dist/ survives for attestation.
Drop continue-on-error so a missing SBOM gates publish rather than
shipping silently.

Build provenance was unaffected (runs before checkout, never
continue-on-error); PyPI publish and the weekly attestation health check
both verify provenance only, which is why this went unnoticed.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 985e6b02-15bd-4b13-b6e6-2560a8a4d60f

📥 Commits

Reviewing files that changed from the base of the PR and between 81e79dd and c02c9d5.

📒 Files selected for processing (1)
  • .github/workflows/release-please.yml

Walkthrough

The attest job in the release workflow replaces the previous multi-step SBOM pipeline (cyclonedx-py, cargo-sbom, optional cyclonedx-cli merge) with a single anchore/sbom-action step. The release tag is checked out into ./source to preserve dist/ contents, and continue-on-error is removed from the "Attest SBOM" step.

Changes

SBOM Pipeline Replacement and Attestation Hardening

Layer / File(s) Summary
SBOM generation and attestation hardening
.github/workflows/release-please.yml
Checks out the release tag into ./source to preserve the dist/ directory; replaces the cyclonedx-py, cargo-sbom, and optional cyclonedx-cli merge steps with a single anchore/sbom-action call that generates sbom.cdx.json; removes continue-on-error: true from the "Attest SBOM" step, making attestation failures non-ignorable.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • cachekit-io/cachekit-py#101: Modifies the same attest job in .github/workflows/release-please.yml, with overlapping changes to checkout and SBOM generation setup.
  • cachekit-io/cachekit-py#116: Also removes continue-on-error: true from attestation-related steps in the same workflow file, directly overlapping with the hardening change in this PR.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically identifies the primary change: fixing a broken SBOM attestation in the CI release workflow.
Description check ✅ Passed The description comprehensively covers Problem, Root cause, Fix, Type, and Verification sections, clearly articulating the SBOM attestation failures and the proposed solution.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/release-sbom-attestation

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release-please.yml:
- Around line 250-253: The actions/checkout step is persisting credentials
unnecessarily, which exposes the job token to subsequent third-party action
execution. Add persist-credentials: false to the with section of the
actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd action, after the
existing ref and path parameters, to prevent the git credentials from being
written to the git config file in the source directory.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 10eba78d-f089-4d8b-9732-3dd6c5414be5

📥 Commits

Reviewing files that changed from the base of the PR and between 9749121 and 81e79dd.

📒 Files selected for processing (1)
  • .github/workflows/release-please.yml

Comment thread .github/workflows/release-please.yml
@27Bslash6 27Bslash6 merged commit e09701a into main Jun 21, 2026
32 checks passed
@27Bslash6 27Bslash6 deleted the fix/release-sbom-attestation branch June 21, 2026 01:43
27Bslash6 added a commit that referenced this pull request Jun 21, 2026
Security review of #201 found the SBOM was generated correctly but with hardening gaps. Apply all four findings: (1) isolate syft (third-party) into a credential-less 'sbom' job so the OIDC signing token in 'attest' is never exposed to untrusted code; (2) exclude tests/** via .github/syft.yaml so the BOM describes shipped deps, not the test harness (syft was cataloging tests/integration/saas/requirements.txt); (3) jq guard fails closed on a valid-but-empty SBOM; (4) disable the action's upload-artifact/upload-release-assets side-effects, handing the SBOM to attest via an explicit artifact. Also gate publish on needs.attest.result=='success': required now that attest is *skipped* (not failed) when sbom fails, which a plain !failure() check would let publish through unattested.
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.

1 participant