You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Publish attested release binaries with build provenance and SBOMs #1235
We need release binaries that publishers and vendors can verify were built from this repo. Today there is nothing to verify. No workflow publishes release artifacts, tags v1.0.0 through v1.1.0 have no GitHub Releases, and every operator builds its own wasm from source. GitHub's built-in supply-chain tooling covers this. Build the binaries in a release workflow, attest SLSA build provenance and an SBOM for each one through Sigstore, and publish them as immutable release assets that anyone can check with gh attestation verify.
Binary attestation takes priority. An attestation only means something if every publisher runs the same bytes, so no configuration may be compiled into the binary. See Constraints.
PR #167 and its proposal, also on main as docs/superpowers/specs/2026-01-15-attestation-design.md, are reference only. #167 will be closed without merging. The proposal predates config separation (#799), the ts CLI and the non-Fastly adapters. This issue covers its build-provenance part. Runtime claims about the running config stay with #161.
Spec first
Write a design spec and get it approved before opening an implementation PR.
Put it at docs/superpowers/specs/YYYY-MM-DD-1235-<slug>-design.md and follow the existing specs there. Include the issue link, date, status, problem, goals, non-goals, design, test plan and rollout. It replaces the binary-attestation sections of 2026-01-15-attestation-design.md.
Land the spec in its own PR. The implementation PR links the approved spec and follows it. If the design changes during implementation, update the spec in the same PR.
The spec states the no-configuration-in-the-binary rule as a requirement and lists every compile-time input of the release binary.
The spec must settle these open decisions:
Which artifacts ship first. The Fastly wasm and the ts CLI for Linux and macOS are a suggestion. Cloudflare, Spin and standalone tsjs bundles can follow.
How operators deploy an attested artifact. ts deploy builds from source today. Either it learns to verify and deploy a release artifact, which likely needs an EdgeZero change, or builds become reproducible so a local build matches the attested digest. Pick one, or both in a stated order.
The SBOM format and generator, and whether the SBOM ships as a release asset.
The release trigger, tag push or manual dispatch, and whether to start with a reusable build workflow for SLSA Build Level 3.
The exact verification commands for publishers and vendors, the identity they pin (repo, workflow path, ref), and the offline flow.
Which build facts the runtime can report for Trusted Server attestation #161, such as version and git commit, given that a wasm module can't hash itself.
No workflow publishes or attests anything. test.yml builds the Fastly wasm (.github/workflows/test.yml:111) and the Spin wasm (test.yml:174) only to run tests.
Operators build from source. ts build and ts deploy hand off to edgezero_cli::run_build and edgezero_cli::run_deploy (crates/trusted-server-cli/src/run.rs:124, run.rs:148), and ts deploy builds before it uploads. The CLI itself installs from source with cargo install-cli (.cargo/config.toml:59).
The wasm on main embeds no operator config. Its compile-time inputs are:
The source tree, Cargo.lock and rust-toolchain.toml (Rust 1.95.0).
The logical config store ID, which crates/trusted-server-core/build.rs:37-38 reads from edgezero.toml and config_payload.rs:23 compiles in. Operators map their physical stores to that logical name with resource links and EDGEZERO__STORES__...__NAME overrides, so the repo's edgezero.toml is the same for everyone.
The tsjs bundles, which crates/trusted-server-js/build.rs builds with npm from package-lock.json and embeds.
crates/trusted-server-js/build.rs falls back to whatever is in dist/, with only a warning, when npm is missing (lines 41-45), when npm ci fails (lines 56-58), or when TSJS_SKIP_BUILD=1 is set (line 23). Only a failed npm run build stops the build (lines 81-84).
Proposed change
Add .github/workflows/release.yml.
Runs on a v* tag, on GitHub-hosted runners only. Every action is pinned by commit SHA, and job permissions are limited to contents: write, id-token: write, attestations: write and artifact-metadata: write.
Builds with --locked and npm installed, and fails if the tsjs bundles weren't built in that run.
Generates the SBOM from the release build so it covers the Cargo crates and the npm packages compiled into the wasm. GitHub's dependency-graph export describes the repo, not a build, so it doesn't fit.
Attests each binary with actions/attest@v4. It creates SLSA build provenance by default and an SBOM attestation when given sbom-path. The repo is public, so signing goes through Sigstore's public-good instance and the entries are public in Rekor.
Creates a draft release, uploads the binaries, the SBOM and a SHA256SUMS file, then publishes it. With immutable releases on, nobody can change the assets or move the tag after that, and GitHub adds its own release attestation.
Attest the .wasm, not a Fastly package. A package also carries fastly.toml, which holds the operator's service ID, so its digest differs per publisher.
Verification for a publisher or vendor then looks like this:
No configuration may be compiled into the release binary. Anything a deployment varies goes through trusted-server.toml and ts config push, or through platform stores. Other features have to fit this rule, not the other way around.
edgezero.toml is a compile-time input, so attested builds use the repo's copy unchanged. Operators who run several services in one account rename physical stores, not logical IDs.
The external Prebid bundle from ts prebid bundle, which operators build and pin by hash in config.
Done when
The design spec is approved and merged under docs/superpowers/specs/, and the implementation PR links it.
The spec states the no-configuration-in-the-binary rule and lists every compile-time input of the release binary.
Pushing a v* tag publishes an immutable GitHub Release with the chosen binaries, the SBOM and SHA256SUMS.
gh attestation verify passes for each binary with --signer-workflow pinned, and for the SBOM with --predicate-type. gh release verify-asset passes for each asset.
The release build fails, instead of reusing dist/, when npm is missing, npm ci fails, or TSJS_SKIP_BUILD is set.
Either building the tag the documented way gives the same wasm digest as the release asset, or the docs say operators must deploy the release asset, matching the spec's decision.
Immutable releases are on, and a tag ruleset limits who can push v* tags.
docs/guide/cli.md, a new page on verifying a release, and CHANGELOG.md are updated.
Summary
We need release binaries that publishers and vendors can verify were built from this repo. Today there is nothing to verify. No workflow publishes release artifacts, tags
v1.0.0throughv1.1.0have no GitHub Releases, and every operator builds its own wasm from source. GitHub's built-in supply-chain tooling covers this. Build the binaries in a release workflow, attest SLSA build provenance and an SBOM for each one through Sigstore, and publish them as immutable release assets that anyone can check withgh attestation verify.Binary attestation takes priority. An attestation only means something if every publisher runs the same bytes, so no configuration may be compiled into the binary. See Constraints.
PR #167 and its proposal, also on
mainasdocs/superpowers/specs/2026-01-15-attestation-design.md, are reference only. #167 will be closed without merging. The proposal predates config separation (#799), thetsCLI and the non-Fastly adapters. This issue covers its build-provenance part. Runtime claims about the running config stay with #161.Spec first
Write a design spec and get it approved before opening an implementation PR.
docs/superpowers/specs/YYYY-MM-DD-1235-<slug>-design.mdand follow the existing specs there. Include the issue link, date, status, problem, goals, non-goals, design, test plan and rollout. It replaces the binary-attestation sections of2026-01-15-attestation-design.md.tsCLI for Linux and macOS are a suggestion. Cloudflare, Spin and standalone tsjs bundles can follow.ts deploybuilds from source today. Either it learns to verify and deploy a release artifact, which likely needs an EdgeZero change, or builds become reproducible so a local build matches the attested digest. Pick one, or both in a stated order.Current behavior
Line numbers are at 8048301.
test.ymlbuilds the Fastly wasm (.github/workflows/test.yml:111) and the Spin wasm (test.yml:174) only to run tests.ts buildandts deployhand off toedgezero_cli::run_buildandedgezero_cli::run_deploy(crates/trusted-server-cli/src/run.rs:124,run.rs:148), andts deploybuilds before it uploads. The CLI itself installs from source withcargo install-cli(.cargo/config.toml:59).mainembeds no operator config. Its compile-time inputs are:Cargo.lockandrust-toolchain.toml(Rust 1.95.0).crates/trusted-server-core/build.rs:37-38reads fromedgezero.tomlandconfig_payload.rs:23compiles in. Operators map their physical stores to that logical name with resource links andEDGEZERO__STORES__...__NAMEoverrides, so the repo'sedgezero.tomlis the same for everyone.crates/trusted-server-js/build.rsbuilds with npm frompackage-lock.jsonand embeds.crates/trusted-server-js/build.rsfalls back to whatever is indist/, with only a warning, when npm is missing (lines 41-45), whennpm cifails (lines 56-58), or whenTSJS_SKIP_BUILD=1is set (line 23). Only a failednpm run buildstops the build (lines 81-84).Proposed change
Add
.github/workflows/release.yml.v*tag, on GitHub-hosted runners only. Every action is pinned by commit SHA, and job permissions are limited tocontents: write,id-token: write,attestations: writeandartifact-metadata: write.--lockedand npm installed, and fails if the tsjs bundles weren't built in that run.actions/attest@v4. It creates SLSA build provenance by default and an SBOM attestation when givensbom-path. The repo is public, so signing goes through Sigstore's public-good instance and the entries are public in Rekor.SHA256SUMSfile, then publishes it. With immutable releases on, nobody can change the assets or move the tag after that, and GitHub adds its own release attestation.Attest the
.wasm, not a Fastly package. A package also carriesfastly.toml, which holds the operator's service ID, so its digest differs per publisher.Verification for a publisher or vendor then looks like this:
Constraints
trusted-server.tomlandts config push, or through platform stores. Other features have to fit this rule, not the other way around.include_str!(crates/trusted-server-core/src/permissions.rs:483at 22262a0), and Add the integration seam design specs #1084 describes the same design. The permission spec has to resolve that. This spec doesn't.edgezero.tomlis a compile-time input, so attested builds use the repo's copy unchanged. Operators who run several services in one account rename physical stores, not logical IDs.Out of scope
trusted-server.toml.ts prebid bundle, which operators build and pin by hash in config.Done when
docs/superpowers/specs/, and the implementation PR links it.v*tag publishes an immutable GitHub Release with the chosen binaries, the SBOM andSHA256SUMS.gh attestation verifypasses for each binary with--signer-workflowpinned, and for the SBOM with--predicate-type.gh release verify-assetpasses for each asset.dist/, when npm is missing,npm cifails, orTSJS_SKIP_BUILDis set.v*tags.docs/guide/cli.md, a new page on verifying a release, andCHANGELOG.mdare updated.Affected area
CI / Tooling