Skip to content

feat(supply-chain): SLSA Build L3 provenance + npm provenance (#3)#29

Merged
awbx merged 1 commit into
mainfrom
feat/supply-chain-slsa
May 19, 2026
Merged

feat(supply-chain): SLSA Build L3 provenance + npm provenance (#3)#29
awbx merged 1 commit into
mainfrom
feat/supply-chain-slsa

Conversation

@awbx
Copy link
Copy Markdown
Owner

@awbx awbx commented May 19, 2026

Closes #3.

Summary

Adds SLSA Build L3 provenance generation on top of the cosign signing from #2, plus npm-native publish provenance for the TypeScript packages. Every release now ships three independent attestation channels, all bound to this repository's GitHub Actions release workflow identity:

Channel Subjects Tool Bound by
cosign (from #2) checksums.txt + container images cosign keyless Sigstore Fulcio cert
SLSA Build L3 every binary + checksums.txt slsa-github-generator reusable workflow SLSA in-toto attestation, signed via Sigstore
npm provenance every @awbx/cronix-* package npm publish --provenance npm registry's native attestation system

What changes

File Change
`.github/workflows/release.yml` goreleaser job exposes a base64-checksums output; new `provenance:` job uses the canonical slsa-github-generator reusable workflow; new `verify-provenance:` smoke job runs `slsa-verifier` against every produced artifact so a broken provenance pipeline fails the workflow rather than shipping silently. npm job adds `--provenance` to pnpm publish.
`.goreleaser.yaml` Release-footer template extended with SLSA + npm verification commands so they appear inline in every release page.
`README.md` §Verify a release rewritten to cover all three channels with explicit "wrong identity" guidance.

Why the smoke verify

The slsa-github-generator reusable workflow is well-tested upstream but its inputs are repo-specific (base64-encoded checksum file, expected subject set). A subtle bug — e.g., GoReleaser changing the columns in checksums.txt, or a future GoReleaser bump producing artifacts that don't match my `shopt -s nullglob` patterns — would silently ship provenance that nobody can verify against the artifacts users download. The smoke job runs the actual `slsa-verifier verify-artifact` call against every published artifact, on the runner, immediately after publish. If it can't verify, the release workflow fails red and the user gets paged at the first broken release, not the first time an adopter tries to verify it.

Out of scope

Test plan

  • CI: `goreleaser snapshot` job passes (the new `goreleaser check` step from supply-chain: cosign-sign release artifacts via GoReleaser #2 validates the updated release.footer template)
  • CI: all 7 required checks green
  • Sanity: only the workflow level has `id-token: write` — the `provenance:` job sets its own per-job permissions block as the slsa-generator docs require, and `verify-provenance:` uses only `contents: read`
  • Sanity: slsa-github-generator pinned to a specific tag (`@v2.0.0`), not a moving major (`@v2`)
  • Sanity: `slsa-verifier/actions/installer` pinned to `@v2.6.0`
  • Sanity: npm publish gets `--provenance` exactly once per package

First real validation

The first tagged release after merge. On that release:

  • The release page should carry these new assets: `cronix-.intoto.jsonl` (the SLSA provenance file)
  • The `verify-provenance` job should print `verified SLSA provenance against N artifact(s)` with N matching the binary count
  • Each `@awbx/cronix-*` npm package page should show a "Provenance" section
  • Running `slsa-verifier verify-artifact ... --source-uri github.com/awbx/cronix --source-tag ` from any local machine should print a green check

Version pins worth knowing

  • `slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0` — bump as the SLSA framework releases newer versions; v2.x is the current major line
  • `slsa-framework/slsa-verifier/actions/installer@v2.6.0` — companion verifier, same project

Add SLSA build provenance generation, end-to-end provenance
verification, and npm-native publish provenance on top of the cosign
signing wired up in #2.

What ships in this change:

- .github/workflows/release.yml — goreleaser job now exposes a hashes
  output (base64 of checksums.txt, which is already in the
  <sha256> <filename> format slsa-github-generator expects); new
  provenance job calls the canonical
  slsa-framework/slsa-github-generator generic_slsa3 reusable
  workflow pinned at v2.0.0, which produces cronix-<tag>.intoto.jsonl
  and uploads it to the GitHub release; new verify-provenance job
  installs slsa-verifier and verifies the produced attestation
  against every binary and the checksum file, failing the workflow
  if anything does not verify. Catches "we generated provenance for
  the wrong subject set" or "the reusable workflow contract changed"
  before a release ships out the door silently.
- .github/workflows/release.yml — npm job adds --provenance to
  pnpm publish so every @awbx/cronix-* package carries an
  npm-native attestation bound to the same workflow identity.
  No new permission needed; id-token: write was already added at
  the workflow level in #2.
- .goreleaser.yaml — release.footer extends to cover SLSA, npm
  provenance, and the previously-shipped cosign verification, so
  every release page surfaces all three verification routes inline.
- README.md — §Verify a release rewritten to cover all three
  attestation channels (cosign, SLSA, npm) with explicit "wrong
  identity" guidance. Calls out that SLSA verify-artifact is the
  canonical CI check because it fails closed if source repo, tag,
  or artifact hash diverge.

Out of scope:

- syft SBOM attached to every release (#4) — different tool, output,
  and verification story; warrants its own PR.
- arm64 image manifest list (#5) — pre-existing gap; needs a
  docker_manifests block in .goreleaser.yaml.
- Promoting the release to "draft" until SLSA verification passes
  and then flipping it public. Cleaner failure semantics but a
  meaningful workflow restructure; defer to follow-up.

Signed-off-by: Abdelhadi Sabani <asabani.work@gmail.com>
@awbx awbx self-assigned this May 19, 2026
@awbx awbx merged commit 14b1812 into main May 19, 2026
8 checks passed
@awbx awbx deleted the feat/supply-chain-slsa branch May 19, 2026 11:37
awbx added a commit that referenced this pull request May 19, 2026
Add SLSA build provenance generation, end-to-end provenance
verification, and npm-native publish provenance on top of the cosign
signing wired up in #2.

What ships in this change:

- .github/workflows/release.yml — goreleaser job now exposes a hashes
  output (base64 of checksums.txt, which is already in the
  <sha256> <filename> format slsa-github-generator expects); new
  provenance job calls the canonical
  slsa-framework/slsa-github-generator generic_slsa3 reusable
  workflow pinned at v2.0.0, which produces cronix-<tag>.intoto.jsonl
  and uploads it to the GitHub release; new verify-provenance job
  installs slsa-verifier and verifies the produced attestation
  against every binary and the checksum file, failing the workflow
  if anything does not verify. Catches "we generated provenance for
  the wrong subject set" or "the reusable workflow contract changed"
  before a release ships out the door silently.
- .github/workflows/release.yml — npm job adds --provenance to
  pnpm publish so every @awbx/cronix-* package carries an
  npm-native attestation bound to the same workflow identity.
  No new permission needed; id-token: write was already added at
  the workflow level in #2.
- .goreleaser.yaml — release.footer extends to cover SLSA, npm
  provenance, and the previously-shipped cosign verification, so
  every release page surfaces all three verification routes inline.
- README.md — §Verify a release rewritten to cover all three
  attestation channels (cosign, SLSA, npm) with explicit "wrong
  identity" guidance. Calls out that SLSA verify-artifact is the
  canonical CI check because it fails closed if source repo, tag,
  or artifact hash diverge.

Out of scope:

- syft SBOM attached to every release (#4) — different tool, output,
  and verification story; warrants its own PR.
- arm64 image manifest list (#5) — pre-existing gap; needs a
  docker_manifests block in .goreleaser.yaml.
- Promoting the release to "draft" until SLSA verification passes
  and then flipping it public. Cleaner failure semantics but a
  meaningful workflow restructure; defer to follow-up.

Signed-off-by: Abdelhadi Sabani <asabani.work@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

supply-chain: SLSA build provenance on every release

1 participant