
From Provision to Shutdown: The Lifecycle of a Tenki Runner

GitHub's artifact attestation feature uses Sigstore to generate SLSA provenance for your builds. Here's how it works, what it proves, and what changes as defaults tighten.
Every time you run a GitHub Actions workflow that produces a binary, a container image, or a package, there's a question downstream consumers can't easily answer: did this artifact actually come from the repository and workflow it claims to come from? Code signing with long-lived keys has been the traditional answer, but it creates its own problems. Keys get leaked, rotated poorly, or stored in CI secrets that are one misconfiguration away from exposure.
GitHub's artifact attestation feature takes a different approach. Built on Sigstore, it generates cryptographically signed provenance statements that tie an artifact to a specific workflow run, commit, and repository. No long-lived keys. No key management overhead. The signing happens with ephemeral certificates that live for ten minutes, and the proof lands on an immutable transparency log.
This isn't new, exactly. Artifact attestations went GA in June 2024. But what's changing is the default posture. GitHub has been steadily tightening enforcement through 2025 and into 2026, and for public repositories, attestation generation is shifting from opt-in to default behavior. If your workflows produce release artifacts and you haven't thought about attestations yet, you're about to encounter them whether you planned to or not.
An artifact attestation is a signed statement that says: this artifact, identified by its SHA-256 digest, was produced by this specific GitHub Actions workflow run, in this repository, from this commit. The statement follows the in-toto attestation format and includes a predicate containing the build provenance metadata: the workflow file, the runner environment, the triggering event, and the OIDC token claims from the Actions runtime.
This is different from traditional code signing in a few important ways. Code signing answers the question "who vouches for this artifact?" Attestations answer "where and how was this artifact built?" A signed binary tells you that someone with a particular key blessed it. An attestation tells you the exact workflow, commit, and environment that produced it. You can verify both independently.
What attestations don't prove: that the source code is safe, that the dependencies are free of vulnerabilities, or that the build process itself is hardened. Provenance is a paper trail, not a security audit. It lets you trace an artifact back to its origin, which is the foundation for policy decisions, but it doesn't make those decisions for you.
The machinery under the hood involves three Sigstore components working together: Fulcio (certificate authority), a timestamp authority, and Rekor (transparency log). Here's the actual flow when your workflow runs actions/attest-build-provenance:
For private repositories, the flow is identical except GitHub's own internal Fulcio instance issues the certificate, and nothing touches public logs. The attestation stays in GitHub's private store. No customer data leaks to external infrastructure.
GitHub established itself as a root certificate authority in October 2023 to make this work. The trust root is managed through a quorum of hardware tokens held by employees in different geographies and roles, using TUF (The Update Framework) tooling. Intermediate certificates sit in Azure KeyVault Managed HSMs. Because signing certificates are only valid for 10 minutes, certificate revocation lists aren't needed.
The implementation is six lines of YAML. For a binary artifact, you need the right permissions and a call to the attestation action after your build step:
permissions:
id-token: write
contents: read
attestations: write
steps:
- name: Build
run: make build
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-path: 'dist/my-binary'Three permissions matter here. id-token: write lets the action request the OIDC token from the Actions runtime. attestations: write grants access to GitHub's attestation store. contents: read is needed for checkout. If you forget any of these, the action fails with an unhelpful permissions error.
The subject-path parameter accepts glob patterns, so you can attest multiple files at once. The action computes the digest of each file and creates attestations for all of them.
Container images need slightly different handling. Instead of a file path, you provide the fully-qualified image name and its SHA-256 digest. You also need packages: write permission so the attestation can be pushed to the registry alongside the image.
permissions:
id-token: write
contents: read
attestations: write
packages: write
steps:
- name: Build and push image
id: push
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/my-org/my-app:latest
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
with:
subject-name: ghcr.io/my-org/my-app
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: trueTwo things to watch here. First, subject-name must not include a tag. Just the registry, org, and image name. Second, the digest comes from the docker/build-push-action output. This is the immutable identifier for the image. Tags are mutable and can be re-pointed; digests can't. The attestation binds to the digest, which is why verification stays reliable even if someone retags the image.
Setting push-to-registry: true stores the attestation in the OCI registry alongside the image itself, making it discoverable by tools that pull from the registry. This also works with registries other than GHCR, like Azure Container Registry.
Build provenance tells you where an artifact came from. An SBOM attestation tells you what's inside it. GitHub provides a separate action for this: actions/attest-sbom@v2. It takes an SBOM file you've already generated and wraps it in a signed in-toto attestation, binding it to a specific artifact.
The action doesn't generate the SBOM itself. You need a separate tool for that. Anchore's Syft is the most common choice and supports both SPDX and CycloneDX formats across most language ecosystems. A typical setup scans the build directory or container image, outputs an SPDX JSON file, then feeds that into the attestation action:
- uses: anchore/sbom-action@v0
with:
path: ./build/
format: 'spdx-json'
output-file: 'sbom.spdx.json'
- uses: actions/attest-sbom@v2
with:
subject-path: 'dist/my-binary'
sbom-path: 'sbom.spdx.json'The signed SBOM attestation gets stored in the same attestation store as provenance records. Downstream consumers can then verify it using the GitHub CLI with a specific predicate type flag. For regulatory compliance, like NIST's SSDF or the EU Cyber Resilience Act, having a signed and verifiable SBOM tied to a specific build artifact closes a gap that many organizations currently fill with ad-hoc spreadsheets or unverified SBOM files dropped into release notes.
Producing attestations is only half the story. The security benefit comes from verification. The GitHub CLI (version 2.49.0+) handles this with gh attestation verify.
For a binary:
gh attestation verify ./my-binary -R my-org/my-repoFor a container image, prefix the image reference with oci://:
docker login ghcr.io
gh attestation verify oci://ghcr.io/my-org/my-app:v1.2.0 -R my-org/my-repoVerifying SBOM attestations requires an extra flag to specify the predicate type, since the default predicate is build provenance:
gh attestation verify ./my-binary \
-R my-org/my-repo \
--predicate-type https://spdx.dev/Document/v2.3Add --format json to get machine-readable output you can pipe into policy engines. This is where attestations become genuinely useful in automated pipelines. You can feed the JSON into OPA (Open Policy Agent) or Cue to enforce rules like "only deploy artifacts built from the main branch" or "only accept images built by workflows in approved repositories."
For Kubernetes environments, GitHub provides a Sigstore Policy Controller that can enforce attestation verification as an admission controller. You install it via Helm, configure a trust root pointing to GitHub's certificate authority, and label the namespaces where enforcement should apply. Any image without a valid attestation from your organization gets rejected at admission time.
SLSA (Supply-chain Levels for Software Artifacts) defines four build levels, from L0 (no guarantees) through L3 (hardened builds). GitHub's artifact attestations achieve SLSA v1.0 Build Level 2 out of the box.
What does that mean concretely? Build L2 requires that builds run on a hosted platform (not a developer's laptop) and that provenance is signed and tied to that platform's identity. GitHub Actions meets both requirements. The hosted runners provide the infrastructure, the OIDC token provides the identity binding, and Sigstore provides the signature.
Build L3 requires stronger isolation: the build platform must prevent runs from influencing each other, and the signing key material must be inaccessible to user-defined build steps. Standard GitHub Actions workflows don't meet this because the build steps and the attestation step run in the same job, meaning a malicious build step could theoretically interfere.
To reach Build L3, GitHub recommends using reusable workflows. When the build runs inside a reusable workflow called by your repository's workflow, the build logic is isolated from the caller. The reusable workflow controls the build environment and the attestation step, so the calling workflow can't tamper with either. This provides the isolation that L3 demands.
For most teams, L2 is a solid starting point. It stops the most common attack vectors: someone swapping out a binary after the build, or uploading a release artifact that wasn't actually produced by CI. L3 matters more when you're distributing software to environments with strict compliance requirements or when you need to protect against compromised repository credentials.
GitHub has been moving toward making attestation generation automatic for public repositories. The direction is clear from the trajectory: artifact attestations launched in beta in May 2024, went GA a month later, and through 2025 GitHub added enforcement features including the Kubernetes admission controller, linked artifacts pages with build history, and organization-level attestation policies.
When attestation generation becomes the default for public repos, a few things change for maintainers:
attestations: write explicitly to keep things working correctly.If you're starting from scratch, here's a reasonable approach:
gh attestation verify before deploying. Start in audit mode (log failures but don't block) and switch to enforcement once you're confident the attestation pipeline is reliable.@v3 is fine for readability, but your security-critical workflows should reference the immutable SHA.The underlying point is that attestations aren't a checkbox feature you enable and forget. They're useful exactly to the degree that someone on the other end is actually verifying them. If you're producing software that other teams or organizations consume, the question isn't whether to generate attestations. It's whether you'll adopt the workflow on your own timeline or scramble to add it when a consumer or auditor requires it.

From Provision to Shutdown: The Lifecycle of a Tenki Runner

What Are GitHub Actions Runners? A Complete Beginner’s Guide to CI/CD Workflows

How to Migrate from GitHub-hosted Runners and Save on Cloud Costs in 3 Minutes
Get Tenki
Change 1 line of YAML for faster runners. Install a GitHub App for AI code reviews. No credit card, no contract. Takes about 2 minutes.