Skip to content

fix(release): scope attested SBOM to the shipped runtime closure - #307

Merged
mtskf merged 4 commits into
mainfrom
chore/sbom-scope-shipped-payload
Jul 31, 2026
Merged

fix(release): scope attested SBOM to the shipped runtime closure#307
mtskf merged 4 commits into
mainfrom
chore/sbom-scope-shipped-payload

Conversation

@mtskf

@mtskf mtskf commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Summary

The publish workflow generated its SPDX SBOM from path: . (the whole build-input graph — devDependencies, lockfile, full node_modules) and cryptographically bound it to the shipped .vsix digest. A consumer running gh attestation verify therefore saw vitest/biome/@types/* as if they ship, producing false-positive vulnerability matches. This re-scopes the attested SBOM to the shipped runtime dependency closure only.

Changes

  • publish.yml: before packaging, assemble a prod-only dependency tree in a throwaway $RUNNER_TEMP/sbom-src staging dir (pnpm install --prod --frozen-lockfile, lockfile then removed) and point anchore/sbom-action at it. Result: exact resolved versions, the full runtime closure (incl. transitive @marijn/find-cluster-break/crelt/style-mod/w3c-keyname), and zero devDependencies. package.json + pnpm-lock.yaml + pnpm-workspace.yaml + .npmrc are copied so --frozen-lockfile resolves identically to the real install.
  • SBOM step stays before Package .vsix, so audit-vsix.mjs remains the last gate before attest + publish (the third-party action never sees the .vsix) — the existing security invariant is preserved, not reordered.
  • New scripts/verify-sbom-scope.mjs gate (run after SBOM generation, fail-closed): rejects the release if the SBOM leaks build-only tooling, drops a declared or known-transitive runtime dep, or carries non-exact versions.
  • test/build/verify-sbom-scope.test.ts: 19 unit tests pinning the gate's four branches (non-vacuous).

Scope note: this lists the prod dependency closure — a conservative superset of the esbuild-bundled set (it also lists prod-transitive deps that get tree-shaken out). A byte-exact bundled SBOM would need a hand-rolled SPDX generator off the esbuild metafile; deferred as a documented future refinement.

Related

  • TODO: publish.yml SBOM catalogs the whole repo tree, then gets attested against the .vsix (full-codebase review round 2)

Test Plan

  • pnpm exec vitest run test/build/verify-sbom-scope.test.ts — 19 pass
  • Gate integration-checked against a synthesized SBOM built from the real 23-package prod closure: true closure passes (exit 0); dev-tooling injection, degenerate scan, and range versions each fail (exit 1)
  • pnpm compile clean; workflow YAML valid; step order Assemble → Generate SBOM → Verify SBOM → Package → Audit .vsix → Attest
  • CI green on this PR

mtskf added 4 commits July 31, 2026 23:43
Generate the attested SBOM from a prod-only staging tree (exact resolved
versions, runtime dependency closure only) instead of scanning the whole
repo, so a consumer verifying the .vsix attestation no longer sees
devDependencies (vitest/biome/@types/*) that never ship. The SBOM step
stays before Package .vsix, preserving the audit-vsix last-gate invariant.
A new scripts/verify-sbom-scope.mjs gate fails the release if the SBOM
leaks dev tooling, drops a declared/known-transitive runtime dep, or
carries non-exact versions.
- verify-sbom-scope: null-guard sbom (sbom?.packages) so a JSON.parse("null")
  SBOM fails closed via the CLI exit-1 + ::error:: path instead of throwing
  an uncaught TypeError that bypasses it.
- verify-sbom-scope test: add a non-npm (pypi) purl fixture with a range
  version so the pkg:npm/ discriminator is non-vacuous, plus a null-sbom
  degenerate-scan case.
- publish.yml: correct the 'ONLY third-party action' comment — pnpm/action-setup
  is also third-party; the accurate claim is 'only third-party action on the
  build to package to attest critical path'.
@mtskf
mtskf merged commit 3ae221e into main Jul 31, 2026
1 check passed
@mtskf
mtskf deleted the chore/sbom-scope-shipped-payload branch July 31, 2026 14:18
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