Skip to content

Attest the web build so the shipped bytes can be verified - #2

Merged
lgoyal6 merged 3 commits into
mainfrom
attest/build-provenance
Sep 6, 2026
Merged

lgoyal6 merged 3 commits into
mainfrom
attest/build-provenance

Conversation

@lgoyal6

@lgoyal6 lgoyal6 commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

What this changes

Adds .github/workflows/attest.yml, the repository's first workflow. On every push it installs web/ from the lockfile, runs next build, packs .next into 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 .next was a directory somebody produced with no statement tying it to a commit or a workflow.

web/.next is 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:

  • positive gh attestation verify <artifact> --repo <repo> exits 0 for the exact bytes the job built
  • negative flipping one bit and re-verifying fails, both against the repository's attestation store and against the good artifact's own downloaded bundle
  • negative the intact artifact verified against a different repository fails, so a build cannot be passed off as another project's

An 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

lgoyal6 and others added 3 commits September 5, 2026 17:33
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>
@lgoyal6
lgoyal6 merged commit 2107b10 into main Sep 6, 2026
1 check passed
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