How to Use Artifacts and Build Provenance the Right Way
Produce CI build artifacts you can trust: checksums, retention, signed attestations, SLSA provenance, reproducible builds and verifying before deploy.

When a binary or container image arrives on a server, how do you know it came from your repository's main branch and not from a laptop or a compromised runner? Provenance answers that: a signed statement from the build system describing what was built, from which commit and by which workflow. It is a modest addition to a pipeline and it closes a real gap in the supply chain. It follows building and pushing images in CI. The examples use GitHub Actions and the gh CLI, current as of 2026.
Upload with checksums
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
attestations: write
steps:
- uses: actions/checkout@v4
- run: make dist
- run: sha256sum dist/* > dist/SHA256SUMS
- uses: actions/attest-build-provenance@v2
with:
subject-path: dist/*
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 14The workflow builds once and records the digests of the files. The attest step signs a statement using the workflow's OIDC identity, with no long-lived signing key to manage, and stores it with the repository. The permissions are narrow: read access to contents, plus the two needed to sign. Pin third-party actions to a full commit SHA in sensitive repositories; a tag can be moved.
Verify before deploying
gh run download 123456789 -n dist -D dist/
(cd dist && sha256sum -c SHA256SUMS)
gh attestation verify dist/app-linux-amd64 --repo example/appapp-linux-amd64: OK
Loaded digest sha256:3f1a... for file://dist/app-linux-amd64
✓ Verification succeeded!
Signer workflow: example/app/.github/workflows/release.yml@refs/heads/main
The checksum catches corruption in transit, and the attestation check proves the signer: the file was built by that workflow, on that branch, in that repository. Run both in the deploy job, and refuse anything that fails. The result lines above are representative of the tool's output. Verifying container images uses the same command against the registry, or cosign verify for signatures.
Deploy what you tested
build -> test (uses artifact) -> deploy (same artifact)
NOT: build -> test -> rebuild on deploy
Rebuilding at deploy time produces a different binary, possibly with different dependency versions, so the tests proved something about a thing that is no longer used. Pass the artifact between jobs, or promote a container image by digest instead of rebuilding it. Tag releases from the tested commit, using annotated tags, and attach the artifacts and the checksums to the release, as in GitHub Releases for distribution.
Make builds repeatable
FROM debian:13-slim@sha256:0123...example
RUN apt-get update && apt-get install -y --no-install-recommends make=4.4.1-1
Pin base images by digest and tool versions, and lock dependency versions with lock files. A reproducible build, one that yields the same bytes from the same source, lets independent parties verify your output; it needs fixed timestamps (SOURCE_DATE_EPOCH), sorted file lists and no environment-dependent paths (-trimpath in Go). It is hard for some ecosystems and worth it for security-sensitive tools. The digest shown is a placeholder.
Retention and storage
Intermediate artifacts, test reports and caches belong on short retention (days), and release artifacts in a registry or release page that you control. Artifacts attached to a pipeline are readable by anyone with access to the repository's runs, so never include secrets or customer data in them, a rule shared with logs.
What provenance adds beyond a checksum
A checksum says that the bytes you hold match the bytes someone published. It cannot say who produced them or how. If an attacker replaces the file and the checksum together, which is easy when both live in the same place, verification passes. Provenance binds the artifact to an identity and a process: a statement, signed by the build platform, naming the repository, the commit, the workflow file and the build parameters. To forge it, the attacker needs to compromise the platform's signing identity rather than just the storage bucket, which is a much higher bar.
The statement is also an audit record. When a vulnerability is announced in a library, you can query which artifacts were built from commits that included it, because each attestation lists the source and the dependencies at build time. Without it, answering that question means guessing from release notes and file dates.
Where verification belongs
Verifying at the point of use is what makes the scheme effective. A signed artifact that nobody checks protects nothing. Put the verification in the deploy job and in any installer you publish, and make it fail closed: if the attestation is missing or the signer is not the expected workflow, the deployment stops. For container clusters, an admission controller can enforce the same rule centrally, so a manifest that references an unsigned image is rejected before a pod is created, whatever the deploy script did.
Be precise about what you accept. Verifying only that the artifact was signed by some GitHub workflow accepts any repository's workflow. Pin the repository, the workflow path and the branch or tag in the verification command, so only builds from your release workflow on the main branch are trusted. The extra flags turn a weak check into a specific one.
Protecting the build itself
Provenance is only as trustworthy as the build it describes. If a pull request from a fork can run a workflow with write access and secrets, an attacker can produce a validly signed malicious artifact. Restrict who can trigger release workflows, require reviews on changes to workflow files, and avoid the trigger that runs untrusted code with privileged tokens. Use ephemeral runners for builds that sign, because a long-lived self-hosted runner keeps state between jobs and can be poisoned by an earlier one.
Dependencies enter the build too. Install them from a lock file with hash verification, vendor them when practical, and fetch tools by pinned version and checksum rather than by running a script from the internet. An SBOM generated in the same job and attached with the attestation records what was inside the artifact, which turns a later vulnerability check into a lookup.
Rolling it out gradually
Start in report-only mode. Generate and publish attestations for a few releases while verification only logs its result, fix the surprises, and then turn on enforcement. Document the verification command in the README so users can check downloads themselves. The first release with provenance is the hardest, and after that the pipeline produces it every time with no extra effort from anyone.
A worked example of the full chain
Consider a release of a command-line tool. A tag push starts the release workflow, which checks out the tagged commit, builds each platform from a pinned toolchain, writes the checksum file and attests every binary. The workflow uploads the files to the release page. A user, or the team's own deploy job, downloads the binary, runs the verification command with the repository and workflow name, and installs it only if the check passes. At no point does anyone hold a private signing key, and every step is repeatable from the tag.
Failure is informative at each point. A binary built on a laptop and uploaded by hand has no attestation, so verification fails and the release is not trusted. A binary rebuilt from a different commit fails because the recorded commit does not match the expected tag. A workflow edited in a pull request and run from a branch fails because the signer is not the pinned path on the main branch. Each of these is a real incident in other organisations, and each is caught by a single command at the end of the chain.
Record the verification result in the deploy log, together with the digest and the signer. If a question comes up later about what was running in production on a given day, the log answers it with evidence, not with memory of what the pipeline was supposed to do.
Where this does not apply
Provenance proves where an artifact was built, not that its code is safe: a malicious commit on main yields a perfectly attested malicious binary. It reduces risk from compromised build machines and from tampering in transit. Small internal tools may not need signing, but checksums cost nothing. See the SLSA specification and the GitHub artifact attestations guide.


