Attesting Builds Directly From GitHub Actions
The generally available attest-build-provenance action attaches cryptographic provenance to any artifact generated in a workflow. This covers executables, language packages (npm, NuGet, Maven), container images in registries such as Docker Hub, GitHub Packages, or Azure Container Registry, and even plain .zip archives. The attestation ties the binary back to its exact source commit, helping teams satisfy SLSA v1.0 Build Level 2 and internal compliance reviews.
Adding the action takes one step after the build step: supply the path to the artifact you want attested.
name: build-attest
on:
workflow_dispatch:
jobs:
build:
permissions:
id-token: write
contents: read
attestations: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build artifact
run: make our-app
- name: Artifact Attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: '${{ github.workspace }}/our-app'
You can also direct the attestation to an OCI image registry or override the default token, as documented in the action repo.
- uses: actions/attest-build-provenance@v1
with:
# Path to the artifact serving as the subject of the attestation. Must
# specify exactly one of "subject-path" or "subject-digest". May contain a
# glob pattern or list of paths (total subject count cannot exceed 2500).
subject-path:
# SHA256 digest of the subject for the attestation. Must be in the form
# "sha256:hex_digest" (e.g. "sha256:abc123..."). Must specify exactly one
# of "subject-path" or "subject-digest".
subject-digest:
# Subject name as it should appear in the attestation. Required unless
# "subject-path" is specified, in which case it will be inferred from the
# path.
subject-name:
# Whether to push the attestation to the image registry. Requires that the
# "subject-name" parameter specify the fully-qualified image name and that
# the "subject-digest" parameter be specified. Defaults to false.
push-to-registry:
# The GitHub token used to make authenticated API requests. Default is
# ${{ github.token }}
github-token:
After the workflow succeeds, the attestation appears under the Actions tab's "Attestations" pane. From there you can inspect build metadata or download the attestation file.
Verification happens from a terminal with the GitHub CLI. Point it at the original artifact path—this is easy to copy into an automated verify step.
gh attestation verify PATH/TO/ARTIFACT -o myorganization
Re-running this check in CI gives you an auditable confirmation that the artifact in hand matches what the workflow actually produced.
Enforcing Attestations at the Cluster Boundary
Attesting containers is only half the problem: you still need to stop unverified images from reaching your cluster. GitHub distributes a Kubernetes admission controller for this, packaged as two Helm charts. One installs the Sigstore policy controller that performs verification; the other installs GitHub's trust root and a default cluster image policy.
First, confirm the chart is genuinely from GitHub by inspecting its provenance—the output will show the expected creating repository.
gh attestation verify --owner github \
oci://ghcr.io/github/artifact-attestations-helm-charts/policy-controller:v0.10.0-github5
Then install both charts, setting policy.organization to your own org when installing the trust policies chart. The chart's values file exposes a full set of tunable cluster image policy options.
helm install policy-controller --atomic \
--create-namespace --namespace artifact-attestations \
oci://ghcr.io/github/artifact-attestations-helm-charts/policy-controller \
--version v0.10.0-github5
helm install trust-policies --atomic \
--namespace artifact-attestations \
oci://ghcr.io/github/artifact-attestations-helm-charts/trust-policies \
--version v0.5.0 \
--set policy.enabled=true \
--set policy.organization=MY-ORGANIZATION
Enforcement is not automatic; you enable it per namespace with an annotation, so different namespaces can have different levels of scrutiny.
metadata:
annotations:
policy.sigstore.dev/include: true
The equivalent kubectl command is faster if you prefer not to edit YAML:
kubectl label namespace MYNAMESPACE
policy.sigstore.dev/include=true
When a deploy is attempted, the policy controller evaluates the incoming image's attestation data in JSON format against the policy's expectations. Only images carrying valid attestations from your org pass admission; everything else is rejected before it ever touches a node.
Once the namespace annotation is in place, unverified images are simply blocked—which is exactly the behavior you want when the question of "is this really our build?" is answered automatically at every deploy.



