Skip to content

ci: drop the SBOM release-asset job, keep the attestation (LAB-983) - #64

Merged
27Bslash6 merged 6 commits into
mainfrom
lab-983-least-privilege-sbom-upload
Aug 8, 2026
Merged

ci: drop the SBOM release-asset job, keep the attestation (LAB-983)#64
27Bslash6 merged 6 commits into
mainfrom
lab-983-least-privilege-sbom-upload

Conversation

@27Bslash6

@27Bslash6 27Bslash6 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Removes the SBOM release-asset upload job from the security workflow, which was fundamentally broken due to immutable releases being enabled on this repository.

Changes

.github/workflows/security.yml

  • Removed the entire sbom job that generated an SBOM and attempted to upload it as a release asset. This job always failed with "Cannot upload assets to an immutable release" because immutable releases seal a release's assets at publish time — every release since v0.1.0 has had zero assets.
  • Removed the release: [published] trigger, which existed solely for that now-deleted job. A detailed comment explains why it was dropped and what would be required to re-enable asset uploads (creating the release as a draft, attaching, then publishing).

SECURITY.md

  • Added a new "Software Bill of Materials" section documenting that the CycloneDX 1.6 SBOM is generated by cargo-sbom and attested via actions/attest-sbom during publish.
  • Provided verification instructions using gh attestation verify, including a note about the predicate type (https://cyclonedx.org/bom) carrying no version suffix.
  • Clarified that GitHub releases carry no downloadable SBOM asset and that users should rely on the attestation instead.

Purpose

The SBOM remains generated and attested in release.yml — that attestation is the verifiable, signed artifact. The removed release asset would have been an unsigned, weaker copy that could never be uploaded anyway. This change eliminates empty/failing workflow runs and documents the correct way to obtain and verify the SBOM.

The Generate SBOM job had no permissions: block, so it inherited the repo
default of read-only and every release: published run died on "Resource not
accessible by integration" at the asset upload. cachekit-core-v0.4.0 shipped
with zero release assets.

Adding contents: write to that job would have been the wrong fix: it runs
cargo sbom, which compiles and executes third-party build scripts and
proc-macros, and a release-write token must not share a runner with untrusted
code. Split instead, matching cachekit-py's sbom job — generation stays
credential-less at contents: read, the SBOM crosses as a workflow artifact,
and a separate sbom-upload job holds the only write token while running
nothing but first-party actions.

Also in this path:

- Replace actions/upload-release-asset (archived by GitHub 2021-03-03, and
  running Node 20 under a forced Node 24 runtime) with actions/github-script.
  gh is not an option: the cachekit ARC runner has no gh CLI (LAB-899).
- Delete the cargo install cargo-sbom, rust-toolchain and cache steps. The
  runner image bakes Rust stable and cargo-sbom, the plain install exits 101
  against the runner's persistent CARGO_HOME, and this job compiles nothing.
- Guard against an empty SBOM. An empty-but-valid CycloneDX document looks
  like a verified supply chain and isn't. The guard also fails closed on an
  unparseable file, which the naive form does not: a failed command
  substitution leaves count empty, [ "" -lt 1 ] errors instead of comparing,
  and the step exits 0.
- Switch to cyclone_dx_json_1_6 so the asset matches the format release.yml
  attests, and so the guard can count .components.
- Add a release_tag dispatch input. Without it this path could only ever be
  exercised by cutting a release, which is how v0.4.0 stayed broken. It also
  backfills v0.4.0. An SBOM dispatch no longer drags Kani onto the ARC pool.
- Check out the released tag rather than main: an SBOM describing the wrong
  tree is worse than a missing one.
Three findings from the panel:

- The workflow concurrency group did not distinguish backfill dispatches, so a
  second dispatch (or a scheduled run on the same ref) could cancel one
  mid-flight. Because sbom-upload deletes a pre-existing asset before
  re-uploading, a cancel landing in that window would leave a release with its
  SBOM deleted and never replaced — worse than the missing asset this fixes,
  and hidden behind a "cancelled" status. release_tag is now part of the key.
  Uses github.event.inputs rather than inputs: the group is evaluated on every
  event and the inputs context is only documented for dispatch/reusable calls.

- The target tag was not checked against a real release until the upload job,
  so `cargo sbom` would compile and execute third-party build scripts for any
  ref a dispatch named, and a typo cost a full generation run before failing.
  Resolution moved ahead of the checkout, while the workspace is still empty.

- The comment justifying the deleted install steps cited release.yml's
  persistent-CARGO_HOME rationale, which is itself stale there — that job moved
  to ubuntu-latest. Reworded to stand on the runner image instead. release.yml
  is out of scope for this ticket, so its stale comment is left alone.

Resolving the release id in the generate job and passing it to the upload job
also drops a redundant getReleaseByTag and closes the window where a repointed
tag could put this SBOM on a different release than it was generated from.

Rejected: extending the backfill exclusion to deep-fuzz. run_deep_fuzz is
already an explicit opt-in, and silently ignoring an input the operator ticked
is worse than honouring it. The kani exclusion is not the same case — kani runs
on any dispatch with no opt-in at all.
Live verification of the split found the wall behind the permissions bug:
immutable releases are enabled on this repository, and GitHub applies
immutability at publish. `release: published` therefore fires only ever against
a sealed release, and the upload returns "Cannot upload assets to an immutable
release". Verified in run 30359945625 - Generate SBOM green in 19s, Attach SBOM
to Release red on that error. Every release in this repo reports
immutable: true and none has ever carried an asset; cachekit-rs is the same,
cachekit-py and cachekit-ts are not.

That is a policy decision, not a retry: publishing an SBOM asset needs either
immutable releases turned off for this repo, or releases created as drafts with
assets attached before publish (which lives in release.yml, out of scope here).
So the target release is now checked before generation runs, and the job fails
with that choice spelled out instead of burning a generation run and dying in
48KB of octokit output.

SECURITY.md documents what the repo actually ships: the attested SBOM, which is
real and verifiable today, plus an explicit statement that releases carry no
SBOM asset and why. The earlier wording in this branch promised an asset that
cannot exist - an unverifiable claim in a security document is a trust bug, and
the expert panel flagged it before this run confirmed it.
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The release security workflow no longer triggers on published releases or uploads SBOM assets. SECURITY.md documents CycloneDX 1.6 SBOM attestation verification for the published crate.

Changes

Release SBOM handling

Layer / File(s) Summary
Release SBOM workflow and verification documentation
.github/workflows/security.yml, SECURITY.md
The workflow removes the release-only SBOM generation and asset-upload job. The documentation describes SBOM attestation verification, predicate details, separate provenance attestation, and the absence of downloadable SBOM release assets.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: removing the SBOM release-asset job while retaining attestation.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lab-983-least-privilege-sbom-upload

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

Immutable releases stay enabled on this repository, so the SBOM release asset
cannot exist: GitHub seals a release's assets at publish, and the upload returns
"Cannot upload assets to an immutable release". Verified live in run
30359945625. Every release since v0.1.0 has zero assets - this job has never
once succeeded.

So the job goes, rather than being rewritten to fail more elegantly. The SBOM is
already generated by cargo-sbom and attested against the packaged crate via
actions/attest-sbom in release.yml. That attestation is the cryptographically
verifiable artifact; a plain release asset would have been an unsigned weaker
copy of the same document, which is not worth trading tamper-resistance for.

The `release:` trigger goes with it. `sbom` was the only job gated on it, so
leaving it would produce an empty run on every release. The reason both were
removed is recorded where the trigger used to be, including what re-adding the
asset would actually require (draft release, attach, then publish), so the next
person does not rediscover the wall.

This also removes the archived actions/upload-release-asset (unmaintained since
2021-03-03) from the repository.

SECURITY.md documents what ships: the attested SBOM and how to verify it, plus
an explicit statement that releases carry no SBOM asset and why. A security
document that promises a downloadable SBOM nobody can download is a trust bug.
The predicate type was wrong. actions/attest-sbom records CycloneDX as
`https://cyclonedx.org/bom` with no version suffix - the spec version lives in
the document's own specVersion field. Only SPDX versions its predicate type.
The documented command therefore 404'd for every consumer who ran it:

  HTTP 404: .../attestations/sha256:6aba15...?predicate_type=...%2Fbom%2Fv1.6

Verified end to end against the real published artifact rather than from
reasoning: downloaded cachekit-core-0.4.0.crate from static.crates.io, confirmed
the recorded attestations for its sha256 are `https://cyclonedx.org/bom` and
`https://slsa.dev/provenance/v1` with subject cachekit-core-0.4.0.crate, and
confirmed the corrected `gh attestation verify` exits 0 with a signature from
release.yml@refs/heads/main. The wrong form errors, the corrected form passes.

The example is now concrete and copy-pasteable - it names the actual crate and
includes the download - because a placeholder like <crate-file> left the reader
guessing which artifact to verify. Also notes that provenance is attested
separately under the SLSA predicate.

The wrong string was copied from attestation-check.yml, which carries the same
bug in both its file-based and list-based SBOM checks. That file is LAB-984's
fail-open territory and out of scope here; noted on that ticket.
@27Bslash6 27Bslash6 changed the title ci: attach SBOM from a least-privilege upload job (LAB-983) ci: drop the SBOM release-asset job, keep the attestation (LAB-983) Aug 5, 2026
@27Bslash6
27Bslash6 marked this pull request as ready for review August 5, 2026 03:54
@kodus-27b

kodus-27b Bot commented Aug 5, 2026

Copy link
Copy Markdown

Kody Review Complete

Great news! 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@SECURITY.md`:
- Around line 86-89: Replace the unsupported gh attestation list invocation in
the no-asset attestation lookup with GET
/repos/{owner}/{repo}/attestations/{subject_digest} via gh api, passing the
subject digest and CycloneDX/provenance predicate filters and preserving real
command failures. Update the asset-path CycloneDX predicate to
https://cyclonedx.org/bom.
🪄 Autofix

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 Plus

Run ID: 77b5edfa-c4b0-4f85-8214-d141a6cfd65e

📥 Commits

Reviewing files that changed from the base of the PR and between e858f5c and c2b0d49.

📒 Files selected for processing (2)
  • .github/workflows/security.yml
  • SECURITY.md

Comment thread SECURITY.md
@27Bslash6
27Bslash6 merged commit 13405d4 into main Aug 8, 2026
31 checks passed
@27Bslash6
27Bslash6 deleted the lab-983-least-privilege-sbom-upload branch August 8, 2026 11:12
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