Attest the web build so the shipped bytes can be verified - #2
Merged
Merged
Conversation
The repository had no workflows at all, so nothing built the site and nothing said where a given build came from. Anyone handed a copy of web/.next had no way to tell it apart from one assembled anywhere else. This adds a push-triggered job that installs from the lockfile, builds, packs .next into one tar so it has a digest at all, and signs a SLSA provenance statement and an SPDX SBOM over that tar. Both are bound to its sha256 digest and to an OIDC identity only this repository can mint, so an attestation cannot be produced out of band. gh attestation verify now passes for the exact bytes the job built and fails for any other bytes, including a single flipped bit. Actions are pinned by commit SHA rather than tag: a tag is a mutable pointer its owner can repoint at different code at any time, so a tag pin does not fix what runs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The scan step wrote its output to the workspace root while the attest step read it from the job's working-directory, so "SBOM file not found" failed the run. An action's paths resolve from the workspace root; defaults.run.working-directory only applies to run steps. Giving output-file the directory prefix puts the file where the attest step already expects it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tar took all of .next, so it carried .next/cache: the incremental build cache, which is not deployed and which differs between runs that produced identical output. That made the artifact hundreds of megabytes and gave the digest a reason to move that has nothing to do with the code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Adds
.github/workflows/attest.yml, the repository's first workflow. On every push it installsweb/from the lockfile, runsnext build, packs.nextinto a single tar, generates an SPDX SBOM with syft, and signs both a SLSA build-provenance statement and the SBOM over that tar.Why
Nothing built this repository on a push, so a compiled
.nextwas a directory somebody produced with no statement tying it to a commit or a workflow.web/.nextis the subject because it is the only thing here a build turns into shippable bytes. The Python half is scripts and Modal entrypoints with no packaging step, so there is nothing there to sign.The tar is written with sorted names and zeroed timestamps and ownership, so its digest changes only when the build output changes.
How it is checked
The attestation is bound to the tar's sha256 digest and to an OIDC identity only this repository can mint, so it cannot be produced out of band. The verification is exercised in both directions:
gh attestation verify <artifact> --repo <repo>exits 0 for the exact bytes the job builtAn attestation nobody tried to break is not evidence, so the controls run every time.
Action pinning
Every third-party action is pinned by commit SHA, not tag. A tag is a mutable pointer its owner can repoint at different code at any time, so a tag pin does not fix what runs.
🤖 Generated with Claude Code