Skip to content

ci(release): attest SLSA provenance over the release manifest's artifacts - #606

Merged
HuiJun merged 2 commits into
developfrom
ci/release-provenance
Sep 26, 2026
Merged

HuiJun merged 2 commits into
developfrom
ci/release-provenance

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Releases now carry signed SLSA provenance over the artifacts they actually publish. The GitHub starter workflow proposed earlier (#414) would have attested two echo'd placeholder files on a platform that does not build the release; this does it in the pipeline that does.

build-release gains two steps after the manifest is signed and verified:

  • scripts/release-provenance.py --manifest dist/SHA256SUMS.txt --out dist/provenance.intoto.json writes an in-toto Statement v1 whose subject is every line of the manifest (name + digest.sha256) and whose https://slsa.dev/provenance/v1 predicate records the build: repository and refs/tags/<tag> as externalParameters, the release commit as a resolvedDependencies git URI, the CircleCI organization/project/workflow as internalParameters, the project as runDetails.builder.id, the job URL as invocationId, buildType https://github.com/Open-MBEE/OpenSysML/.circleci/build-release/v1. It fails closed rather than writing a vaguer statement: empty or malformed manifest lines, duplicate names, any missing CIRCLE_* field, a tag not matching ^v[0-9], or a non-40-hex CIRCLE_SHA1.
  • cosign attest-blob SHA256SUMS.txt --statement provenance.intoto.json --type slsaprovenance1 ... signs that statement as written (cosign keeps the supplied subjects, so one attestation covers every asset) keylessly under the same CircleCI OIDC identity as the manifest, into provenance.intoto.json.bundle.
  • The job then verifies its own output the way a consumer would: cosign verify-blob-attestation against a bundle archive, a sysml-grpc binary and the wheel under the identity regexp the clients pin, and a diff of the manifest's lines against the decoded DSSE payload's subjects, so a statement that names anything other than exactly the manifest's artifacts fails the release before anything is stored.

publish-github-release uploads provenance.intoto.json and provenance.intoto.json.bundle beside SHA256SUMS.txt. The clients are unchanged; they keep verifying the signed manifest.

docs/project/releasing.md documents the consumer check and, under "The release provenance", what the statement does and does not claim: SLSA Build L2 (provenance generated by the build itself, on a hosted runner, signed with an identity only this pipeline holds), not L3, since CircleCI does not issue provenance outside the job.

How it was verified

  • scripts/release-provenance-test.py (13 tests) covers manifest parsing (blank lines, * binary marker, names with spaces, empty/malformed/duplicate lines), the environment validation, the statement's shape and metadata, and the CLI's success and failure paths. It runs in the go-static CircleCI job and the PR workflow next to the changelog check.
  • Offline cosign 3.0.3 experiment with a key pair: attest-blob --statement preserves a two-subject statement; verify-blob-attestation passes for a file whose digest is either subject and fails for one whose digest is not.
  • python3 scripts/changelog.py check, scripts/check-doc-links.py, scripts/check-doc-ids.py pass; both YAML files parse.
  • The keyless signing path itself needs a v* tag on CircleCI and cannot run outside a release; the new steps reuse the token acquisition, issuer, identity regexp and cosign install of the existing manifest-signing steps unchanged.

Checklist

  • make test and make lint pass locally (no Go code changed; the Python test and script checks above pass)
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (none moved)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

…acts

build-release writes an in-toto Statement v1 with a SLSA Provenance v1 predicate whose subjects are every artifact SHA256SUMS.txt lists, signs it with cosign keyless under the pipeline's CircleCI OIDC identity, verifies the attestation against built artifacts and against the manifest's lines, and publishes the statement and bundle beside the manifest.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review September 26, 2026 01:42
devin-ai-integration[bot]

This comment was marked as resolved.

… mid-job

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun merged commit a4fecb6 into develop Sep 26, 2026
18 checks passed
@HuiJun
HuiJun deleted the ci/release-provenance branch September 26, 2026 16:35
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