Skip to content

ci: attach the SBOMs to the GitHub release - #4

Merged
CaffeinatedCoder merged 1 commit into
mainfrom
ci/release-assets-sbom
Aug 16, 2026
Merged

CaffeinatedCoder merged 1 commit into
mainfrom
ci/release-assets-sbom

Conversation

@CaffeinatedCoder

Copy link
Copy Markdown
Owner

Why

SECURITY.md says each release ships a CycloneDX SBOM. It did — as a workflow artifact, which expires after 90 days, and nuget.org has no slot for one. The GitHub releases for v1.0.0–v6.2.0 carry no assets. So the statement holds for one quarter per release and then goes quietly false — the exact shape of claim the security policy warns against overstating.

What

A third job in release.yml, release, runs after publish:

  • Creates the GitHub release if none exists, with this version's section of CHANGELOG.md as the notes — the section ChangelogConventionTests already requires for the shipped version. An empty extraction fails the job rather than publishing a blank release. The heading's [6.2.0] link brackets are dropped (their target is the release page itself). A release created by hand before the tag was pushed is left as written; only the assets are added.
  • Attaches the per-package *.cdx.json files (--clobber, so a re-run is safe).
  • Appends a one-line footer to generated notes linking the workflow run that built and published the version.

Runs after the push, so what is attached describes what is on nuget.org; a failure here is loud and costs a manual upload, nothing more.

Only the SBOMs are attached, deliberately. nuget.org repository-signs every package on ingestion, so a .nupkg downloaded from there never matches ours byte-for-byte. Attaching ours would invite a hash comparison that fails for a benign reason, and nuget.org is already the immutable store for the packages. The SBOM has no other home.

Permissions: release holds contents: write and no id-token; publish holds id-token: write and contents: read. No job holds both — the split that was already the security boundary is kept.

Docs updated to match: SECURITY.md and the README's project-practices line now say where the SBOM lives.

Same change as EFCore.ComplexIndexes#17, with the notes source adapted (README "What changed" section there, CHANGELOG.md here — the README's "What's new in v6.2" is per minor, so a patch release would have no section).

Verified

  • actionlint clean.
  • The two run: blocks were extracted verbatim from the YAML and executed locally with gh release create/upload stubbed and gh release view live: existing release (v6.2.0) → leaves notes alone, exit 0; missing release → creates with the extracted section; view failing for a reason other than "release not found" → fails loudly instead of attempting a create; no CHANGELOG section for the version → fails; no .cdx.json in the artifact → fails.
  • The section extraction was checked against all ten shipped versions: starts at the heading, stops at the next H2.
  • CodoMetis.ValueRanges.Conventions.Tests (31, incl. ReleaseWiringConventionTests) pass.

Not exercised: an actual tag push. The first real run of the job is the next release; if it fails, the packages are already on nuget.org and the SBOMs are still in the run's artifact for a manual upload.

🤖 Generated with Claude Code

SECURITY.md says each release ships a CycloneDX SBOM. It did — as a workflow
artifact, which expires after 90 days, and nuget.org has no slot for one. The
releases for v1.0.0 through v6.2.0 carry no assets, so the statement was true
for one quarter per release and then quietly false.

A third job, `release`, now runs after `publish`: it creates the GitHub
release if none exists (notes taken from this version's section of
CHANGELOG.md, which ChangelogConventionTests already requires for the shipped
version — an empty extraction fails the job rather than publishing a blank
release; the heading's self-referential link brackets are dropped) and
attaches the per-package `.cdx.json` files. A release created by hand before
the tag is pushed is left as written; only the assets are added. It runs
after the push so what is attached describes what is on nuget.org, and a
failure here costs nothing but a manual upload.

Only the SBOMs are attached, deliberately. nuget.org repository-signs every
package on ingestion, so a .nupkg downloaded from there never matches ours
byte-for-byte; attaching ours would invite a hash comparison that fails for
a benign reason, and nuget.org is already the immutable store for the
packages. The SBOM has no other home.

The job holds contents: write and no id-token; publish holds id-token: write
and contents: read. No job holds both.

Verified: actionlint clean; the two run blocks executed locally, extracted
verbatim from the YAML, with `gh release create/upload` stubbed and `gh
release view` live — existing release (v6.2.0) leaves notes alone and exits
0, missing release creates with the extracted section, a version with no
CHANGELOG section fails, and a missing SBOM glob fails. The extraction was
checked against all ten shipped versions. ReleaseWiringConventionTests still
pass. Not exercised: an actual tag push — the first real run is the next
release.

Co-Authored-By: Claude Fable 5 <[email protected]>
@CaffeinatedCoder
CaffeinatedCoder merged commit 5e4d623 into main Aug 16, 2026
3 checks passed
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