Skip to content

Publish attested release binaries with build provenance and SBOMs #1235

Description

@aram356

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.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.

Current behavior

Line numbers are at 8048301.

  • 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:

gh attestation verify trusted-server-fastly.wasm -R IABTechLab/trusted-server \
  --signer-workflow IABTechLab/trusted-server/.github/workflows/release.yml
gh attestation verify trusted-server-fastly.wasm -R IABTechLab/trusted-server \
  --predicate-type https://spdx.dev/Document/v2.3
gh release verify-asset <tag> trusted-server-fastly.wasm

Constraints

  • 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.
  • The permission model in Add the permission model with the Privacy Taxonomy vocabulary #1045 currently breaks the rule. It compiles its policy in with include_str! (crates/trusted-server-core/src/permissions.rs:483 at 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.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.
  • A wasm module can't read its own bytes, so it can't report its own digest at runtime. That belongs to Trusted Server attestation #161. This spec only says which build facts get compiled in for Trusted Server attestation #161 to use.

Out of scope

  • Runtime attestation, config hashes and per-request claims (Trusted Server attestation #161).
  • How permission policy reaches a deployment. The permission spec owns that.
  • Signing or attesting trusted-server.toml.
  • Vendor CODEOWNERS review of integration directories, phase 3 of the Separate config and build for attestation #167 proposal.
  • Signatures embedded in a wasm custom section.
  • 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.

Affected area

CI / Tooling

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions