You cannot trust a build artifact unless you can prove who built it and how. These reusable GitHub Actions workflows build your artifacts (OCI images or blobs), attest them — generating SLSA build provenance (signed, verifiable metadata about how an artifact was built), an SBOM (software bill of materials), and a vulnerability scan — and then verify those attestations against OPA/Rego policy to emit a pass/fail Verification Summary Attestation (VSA) that gates releases. Each step is driven by the autogov CLI, so you get the same supply-chain checks in CI without wiring the tooling together yourself, powered by Sigstore (Rekor/Fulcio/Cosign). Drop the workflows into your repo and call them from your own CI for a paved path to a hardened, policy-gated release pipeline.
These workflows are part of the autogov ecosystem. For the project overview and the CLI that powers them, start with the flagship repo: github.com/liatrio/autogov.
- Quick Start Guide
- Attesting the Workflow Files
- Paving the Path
- Achieving SLSA Build Levels Using Reusable Workflows
- Usage
- Troubleshooting
- Additional Resources
-
Configure Access: Ensure you have the necessary permissions and tokens configured in your remote caller repository described in the access section below.
- Permissions: Ensure you have the necessary permissions to run the workflows.
- Tokens: Set up the required tokens for policy bundle access.
-
Create Your Local Composite Actions: The reusable workflows execute a locally-defined composite action to build your artifact. Copy one of the reference actions into your repo and adjust its build step:
build-image— see.github/actions/build-image/action.yaml. Builds + pushes an OCI image and computes a preemptive semver tag viaautogov release plan; adjust theBuild and pushstep for your image.build-blob— see.github/actions/build-blob/action.yaml. Emits placeholder blobs; replace its build step so it produces your real artifact(s) at the path(s) you pass assubject-path(for an npm package, that's thenpm/yarn packoutput, e.g.liatrio-simple-greeter-1.0.0.tgz).
-
Create Your Caller Workflows / Configure Inputs:
Pick one of the following jobs depending on the desired build type and permissions (
cw-build.yaml):
name: Build Entrypoint Caller Workflow
on:
workflow_dispatch:
push:
jobs:
build-image:
permissions:
id-token: write
attestations: write
packages: write
contents: write
uses: liatrio/autogov-workflows/.github/workflows/rw-build-image.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release
secrets: inherit
with:
registry: ghcr.io
cert-identity: https://github.com/liatrio/autogov-workflows/.github/workflows/rw-attest-image.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release
build-blob:
permissions:
id-token: write
attestations: write
packages: read
contents: write
uses: liatrio/autogov-workflows/.github/workflows/rw-build-blob.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release
secrets: inherit
with:
subject-path: |
i_am_blob
i_am_another_blob
cert-identity: https://github.com/liatrio/autogov-workflows/.github/workflows/rw-attest-blob.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release
build-blob-offline:
permissions:
id-token: write
attestations: write
contents: write
uses: liatrio/autogov-workflows/.github/workflows/rw-build-blob-offline.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release
secrets: inherit
with:
subject-path: |
i_am_blob
i_am_another_blob
cert-identity: https://github.com/liatrio/autogov-workflows/.github/workflows/rw-attest-blob-offline.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release-
Run the Workflow: Trigger the workflow using one of the supported event types:
- [
push] / creation or update of a git tag or branch. - [
release] / creation or update of a GitHub release. - [
create] / creation of a git tag or branch. - [
workflow_dispatch] / enables the ability to trigger workflow manually.
- [
-
Check Results: Review the workflow logs and the uploaded Verification Summary Attestation (VSA) artifact. The
autogovCLI performs signature verification, OPA/Rego policy evaluation, and VSA generation in a single step. A passing run producesvsa-PASSED.json. A failing policy evaluation producesvsa-FAILED.json; the FAILED VSA is still attested and uploaded (the record is preserved), and the job then fails and the release is skipped — unlessallow-failed-vsa: trueis set, which keeps the run advisory. The gate is default-deny: it ships only on an explicitPASSEDresult, so any non-PASSED outcome (FAILED, or a missing/unknown VSA) also blocks the release.
This repository ships reusable workflows, consumed via uses: liatrio/autogov-workflows/.github/workflows/rw-*.yaml@<sha>. Its own cw-build.yaml dogfoods the pipeline: it runs rw-build-blob-offline over the real product — the rw-*.yaml reusables — to attest them (SLSA build-provenance, SBOM, metadata, dependency-scan) and verify them offline into a VSA, then cuts the release attaching that evidence. Signed with the repository's own identity.
Release assets: this repository's own releases ship
cert-identities.json(the signer allowlist), the full attestation bundleautogov.attestations.intoto.jsonl(covering therw-*.yamlreusables), a source-namespaced VSA proving those attestations verified against policy, andautogov-workflows.intoto.jsonl— the single build-provenance statement for OpenSSF Scorecard's release-provenance detection. Release VSAs are always namedvsa-<source>-<result>.json, including single-source releases (vsa-vsa-PASSED.json); combined online/offline verification artifacts produce names such asvsa-verification-summary-attestation-high-perms-PASSED.jsonandvsa-verification-summary-attestation-low-perms-PASSED.json. Therw-*.yamlreusables themselves are not attached as release assets (they're consumed viauses: ...@<ref>and attested by digest in the bundle);rw-build-blob/rw-build-blob-offlineexposerelease-blob-asset(defaulttrue) to control that, and consumer releases built throughrw-build-image/rw-build-blobattach the same evidence shape for their own artifact.
Consumers can verify the publisher identity of a reusable workflow they reference:
gh attestation verify .github/workflows/rw-attest-image.yaml --repo liatrio/autogov-workflowsThis gives publisher identity (cryptographic proof the file was produced by this repository's CI under the GitHub Actions OIDC identity), a signal for consumer policy enforcement, and defense in depth alongside pinning. It does not add file integrity beyond the commit-SHA pin — uses: ...@<sha> already content-addresses the exact file, so the pin is the integrity guarantee; the attestation is about who published the file and how, and is not revoked if the file is later deleted or renamed.
To achieve SLSA Build Level 3, we recommend using GitHub-native tools and reusable workflows. Our approach is inspired by the slsa-framework's implementations, specifically slsa-github-generator and slsa-verifier.
The source repository contains the caller workflow, which interacts with the trusted builder (reusable workflow) to build the artifacts and generate signed provenance; the artifacts and their signed provenance are then securely stored and can be verified to ensure their integrity.
GitHub Artifact Attestations (docs) generate cryptographically signed, in-toto-format attestations of artifact provenance, backed by Sigstore (public repos use the public-good instance, private repos use GitHub's private instance). For the SLSA build-track levels and what each requires, see the SLSA build track.
Often the focus is put upon the "signing" of an artifact, but the source of value lies within verifying artifacts. The gh attestation verify command requires the path to a local or OCI artifact plus an expected --owner or --repo. By default, the CLI does not check the --signer-workflow (a.k.a. --cert-identity) or the source ref — a missing --cert-identity flag is a real fail-open, since the artifact could have been built from a non-approved branch or by a non-approved workflow.
For autogov, supplying --cert-identity (the signer workflow) is mandatory but on by default in our reusable workflows: every verify job passes it, ensuring both the source repository and signer workflow originate from approved branches/tags (e.g. commit SHA) so the artifact is proven to meet SLSA Build Level 3 requirements — as long as whoever verifies remembers to include the flag.
gh-cli does not support verifying the source ref directly, so we combine --jq with grep on sourceRepositoryRef:
- name: Verify Image Attestation(s)
run: |
set +x
gh attestation verify \
oci://${{ inputs.subject-name }}@${{ inputs.image-digest }} \
--repo ${{ github.repository }} \
--deny-self-hosted-runners \
--cert-identity \
"${{ inputs.cert-identity }}" \
--format json \
--jq '.[].verificationResult.signature.certificate.sourceRepositoryRef' \
| grep "^${{ github.ref }}$"The cert-identity input documents the requirement: if verifying an image the workflow name should be rw-attest-image.yaml; if verifying blob(s), rw-attest-blob.yaml (or rw-attest-blob-offline.yaml for the offline path).
This repository maintains a cert-identities.json file that serves as the source of truth for valid certificate identities used by autogov. The file uses a flattened format with a single identities array, where each entry has a status field indicating whether it is:
- latest: Current reusable workflow identities at the current version
- approved: All approved workflow identities (includes previous valid versions)
- revoked: Identities that have been explicitly revoked and should not be used
Each identity consists of a version (e.g. 0.5.1), a commit SHA, a status, an array of workflow identity URLs (including the tag's commit SHA), an addition date and optional expiration date, and revocation details if revoked.
When a new release is tagged, the release creates a tag pointing to a specific commit, and that tag's commit SHA is recorded in the identity entries. An automated workflow then updates cert-identities.json in a separate commit to main — the original tag remains pointing to its initial commit (intentionally). When consuming these workflows, reference them by their full identity URL with commit SHA rather than a branch ref, for immutability and security.
The online and offline verifiers obtain the called workflow's repository and commit from GitHub OIDC, then select the allowlist asset from the unique immutable published release whose tag resolves to that commit. This includes the signer entries generated for that release; the source file at the workflow commit predates those entries. A missing OIDC identity, unmatched or ambiguous release, missing asset, or invalid asset digest fails verification. Callers must grant the existing id-token: write permission and use a published workflow release (tag or full commit SHA). The separate release metadata commit is not that release's workflow commit. cert-identities-repo accepts an ASCII owner/name repository name. GitHub repository IDs determine whether it names the called repository, including rename aliases; only a genuinely different repository uses its default-branch allowlist.
You can also verify bundles with Sigstore's cosign via cosign verify-blob-attestation (note: verifying images this way requires extra tooling such as regctl or Docker to fetch the OCI artifacts).
cosign verify-blob-attestation \
--trusted-root github-trusted-root.json \
--bundle bundle.jsonl \
--use-signed-timestamps \
--insecure-ignore-sct \
--new-bundle-format \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
--certificate-identity="https://github.com/liatrio/autogov-workflows/.github/workflows/rw-attest-blob-offline.yaml@refs/heads/main" \
<path_to_blob>See docs/verification-cosign.md for prerequisites (cosign install, trusted-root generation, ghcr.io login) and the regctl/Docker image-verification paths.
You can use ORAS (OCI Registry As Storage) to inspect and verify artifact attestations — useful for understanding the relationship between image digests and their associated attestation layers. Discover the referrers (attestations) of an image:
oras discover ghcr.io/your-org/your-repo@<image_digest>GitHub stores attestations with the container image in the OCI registry as additional manifests in the OCI index (not separately in GitHub's API); each attestation manifest carries the annotation "vnd.docker.reference.type": "attestation-manifest".
In the attestation manifests you'll see different artifactType values for the different attestations:
application/vnd.dev.sigstore.bundle.v0.3+json: Sigstore bundle formathttps://slsa.dev/provenance/v1: SLSA provenance attestationhttps://autogov.dev/attestation/metadata/v1: Metadata attestationhttps://cyclonedx.org/bom: SBOM attestationhttps://in-toto.io/attestation/vulns/v0.2: Vulnerabilities attestationhttps://autogov.dev/attestation/source-review/v0.2: Source-review (PR-approval + continuity) attestation
See docs/oci-attestation-internals.md for the OCI v1.1 Referrers API, full oras manifest fetch / oras blob fetch walkthroughs, the Sigstore bundle manifest shape, and Docker/regctl inspection notes.
To achieve SLSA Build Level 3, builds and their artifacts must be isolated from one another so that other jobs cannot inadvertently or maliciously alter them. With GitHub Actions this means separating the signing process into its own job, running on GitHub's hardened runners (avoiding self-hosted runners), and abstracting build commands into a Composite Action at a well-defined path. The reusable workflow runs the repository's local composite action to build an image or blob, then attests the artifacts in a separate attesting job.
To isolate job artifacts, the build job uploads the artifact(s) — or, for images, passes the image as a tarball between jobs — to the downstream attest/sign job. The actions/upload-artifact and actions/download-artifact actions support immutable uploads/downloads via artifact IDs (the artifact-ids parameter), so the attest job is guaranteed to operate on the exact artifact, not one another job may have overwritten with the same name. The bundle and trusted-root are likewise passed as artifacts to enable offline verification without extra permissions.
Passing artifacts between jobs also lets us lower permissions. The offline blob workflow (rw-build-blob-offline.yaml) needs no registry access:
attest:
permissions:
id-token: write
attestations: write
packages: read
contents: read
actions: read
verify:
permissions:
contents: readOtherwise (e.g. container-registry access for image builds) the following permissions are used:
attest:
permissions:
id-token: write
attestations: write
packages: write
contents: write
actions: read
verify:
permissions:
id-token: write
attestations: read
packages: read
contents: writeNote: the digest of an exported image tarball (docker save, uncompressed) will always differ from the digest of the pushed (compressed) registry image due to compression, per-layer digesting, and manifest metadata differences. To compare, match the individual layer digests rather than the overall image digest.
To achieve SLSA Build Level 2, builds must run on a hosted platform that generates and signs the provenance. Self-hosted runners can be maliciously modified by their host. Because a self-hosted runner can be labeled ubuntu-latest, the label alone is not enough — so we check the runner context's runner.environment, which is github-hosted for GitHub-hosted runners and self-hosted otherwise, and fail closed:
- name: Fail if Runner is self-hosted
if: ${{ runner.environment != 'github-hosted' }}
run: |
echo "Job is running on a self-hosted runner. Terminating job..."
exit 1Build steps additionally guard with if: ${{ runner.environment == 'github-hosted' }}. We also expose --deny-self-hosted-runners on the gh attestation verify calls. Offering control over the runner label (runs-on: ${{ inputs.workflow-runner-label }}) is acceptable leeway: the OS choice (ubuntu-latest, macos-latest, windows-latest) is unambiguous even if not separately documented as provenance.
To achieve SLSA Build Level 1, the build steps must be consistent so that a verifier "forms expectations about what a 'correct' build" should look like. We check out the source repo by commit SHA rather than only by the ref of the calling workflow, mitigating time-of-check-to-time-of-use (TOCTOU) scenarios where subsequent pushes land between trigger time and checkout:
- name: Checkout code
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
with:
ref: ${{ github.sha }}
persist-credentials: falseSLSA's Provenance Spec requires externalParameters to be complete at Build L3 and MUST be included in provenance at L1. GitHub's build-provenance attestation omits workflow inputs — it records only externalParameters.workflow (path/ref/repository), internalParameters, and resolvedDependencies, not user-supplied inputs (attest-build-provenance#55, tracked upstream in actions/toolkit#2331). To close that gap and prevent script-injection via non-recorded inputs, autogov attests the workflow inputs (and other environment values) itself in a custom metadata predicate generated via actions/attest.
See docs/slsa-workflow-inputs.md for the full discussion, the metadata predicate shape, and the gh attestation verify --jq | grep patterns used to confirm specific inputs were recorded.
pull_request events are not supported: the SLSA GitHub Framework treats them as untrusted because PR code can originate from a fork and modify the build environment or bypass checks. Restricting to push/create/release keeps builds in a controlled, trusted environment. If you would like pull_request support, the maintainers recommend reaching out via slsa-github-generator issue #358.
- actions/attest — generates SLSA build-provenance, the custom cosign generic predicate (metadata), and SBOM attestations in the in-toto format (
actions/attest-build-provenanceis now a wrapper around it). - anchore/sbom-action — generates a software bill of materials (SBOM) using Syft.
- GitHub CLI
gh attestation— verifies and downloads artifact attestations, online or offline. - OCI artifacts / OCI registries — store image attestations as additional content-addressable manifests linked to the image digest (
oci://URIs,permissions.packages). See OCI Image Format Spec. - Sigstore — Rekor/Fulcio/Cosign provide the keyless signing and transparency log underneath GitHub Artifact Attestations.
When viewing your container images in GitHub Container Registry, you might notice additional digests that don't correspond to actual images — these are attestation manifests (metadata proving authenticity and provenance), inspectable via the Verification Using ORAS section.
It is good practice to wrap the actual call to each respective reusable workflow in an additional reusable workflow layer to limit the amount of inputs the user has access to (e.g. inputs for the verify jobs) which helps to circumvent script injection attacks.
Explicit workflow permissions can be set to only allow the "entrypoint" reusable workflows that call other reusable workflows.
Below are all of the GitHub Actions and Workflows that are permitted access in the caller workflow repo. The only reusable workflows not given direct access are rw-<permissions_path>-attest-<build_type>.yaml, rw-<permissions_path>-verify.yaml, and rw-<permissions_path>-release.yaml. Every entry needs the @* ref suffix (or a pinned ref) — a bare owner/repo matches nothing, which blocks the SHA-pinned action and causes a startup_failure:
actions/attest@*,
actions/checkout@*,
actions/download-artifact@*,
actions/upload-artifact@*,
anchore/sbom-action@*,
anchore/scan-action@*,
docker/build-push-action@*,
docker/login-action@*,
docker/metadata-action@*,
docker/setup-buildx-action@*,
docker/setup-qemu-action@*,
octo-sts/action@*,
liatrio/autogov-workflows/.github/workflows/rw-build-blob.yaml@*,
liatrio/autogov-workflows/.github/workflows/rw-build-image.yaml@*,
liatrio/autogov-workflows/.github/workflows/rw-build-blob-offline.yaml@*,It is also necessary to allow access to workflows from other internal/private repositories to avoid having to provide further permissions with the fine grained personal access token discussed below.
access is handled through Chainguard's Octo-STS (the recommended option) / an alternative is creating a fine grained personal access token that has read permissions for the repository and add the appropriate secret and environment variable(s) in the Secrets and Variables section for Actions.
Basic read access can be provided using an autogov-infra.sts.yaml trust policy at the org level (under your org's .github repository), for example:
issuer: https://token.actions.githubusercontent.com
subject_pattern: "repo:liatrio/.*"
permissions:
contents: read
repositories:
- your-policy-library
- your-workflowsFor any additional permissions, a local *.sts.yaml can be created. For example, the creation of the release tag uses the .github/chainguard/release-ops.sts.yaml file:
issuer: https://token.actions.githubusercontent.com
subject_pattern: "repo:liatrio/<your-repo>:ref:refs/heads/main"
permissions:
contents: write
packages: writeMore information about octo-sts can be found in the octo-sts app and the octo-sts/action.
Bring your own auth:
rw-verify.yamlis consumer-configurable — setocto-sts-scope/octo-sts-identity(and, for cross-repo reads,autogov-repo/policy-repo/cert-identities-repo) to your own org and identities; leaveocto-sts-scopeempty to usegithub.token(the default, suitable for public repos). Thescope: liatrio/identity: autogov-infravalues shown in the other octo-sts examples are still liatrio-org-specific and hardcoded insiderw-attest-*(parameterizing those the same way is a tracked follow-up). External orgs must install their own octo-sts app and create equivalent trust policies.Releases (
rw-release.yaml): the binary download (read) and the release cut + asset upload (write) use separate octo-sts pairs.
- Read:
octo-sts-read-scope/octo-sts-read-identity(both default empty →github.token, suitable for public repos).- Write:
octo-sts-release-scope/octo-sts-release-identitydefault toliatrio/release-opsso liatrio's release is unchanged. External orgs override these with their own scope and identity.If your release branch (e.g.
main) is protected by a branch ruleset, the write actor MUST be on that ruleset's bypass list —github.tokencannot bypass a branch ruleset, only an actor on the bypass list can. So external orgs with a protected main must (1) install their own octo-sts GitHub App, (2) add that App to the ruleset's bypass list, and (3) setocto-sts-release-scope/octo-sts-release-identityto a trust policy that issuescontents: writefor that App on the release branch. These same four inputs are threaded throughrw-build-image.yaml/rw-build-blob.yaml/rw-build-blob-offline.yamlto the nestedrw-releasecall.
The setup-autogov composite action installs the Linux X64 autogov binary
from an immutable GitHub release and adds it to PATH. Pin the action itself by
full commit SHA and pass either an immutable release tag or, preferably, the
full 40-character commit SHA referenced by that release:
permissions:
contents: read
steps:
- uses: liatrio/autogov-workflows/.github/actions/setup-autogov@<full-commit-sha>
id: setup-autogov
with:
autogov-version: <autogov-release-commit-sha>
- run: autogov versionautogov-version has no latest-release default. A commit SHA must resolve to
exactly one immutable published release; a supplied tag must also name an
immutable published release. The action checks the release-published SHA-256
digest and verifies GitHub's signed release attestation with
gh release verify-asset before installing the binary. This binds the local
bytes to the immutable release's tag, commit, and asset list; it does not
independently enforce the identity of the workflow that built the binary.
The action requires a stable GitHub CLI release at version 2.93.0 or newer,
a Linux X64 runner, and only contents: read. It checks gh --version before
any API calls and rejects older, prerelease, or unrecognized version strings.
The minimum includes the token-forwarding fix for
GHSA-8xvp-7hj6-mcj9.
For liatrio/autogov, the default github.token is sufficient while the
repository is public. Pass github-token when a different repository requires
an explicit read token. Outputs are path, release-tag, commit-sha, and
sha256.
Vulnerability thresholds (the four
vuln-threshold-*inputs) recur identically onrw-build-image.yaml,rw-build-blob.yaml,rw-build-blob-offline.yaml,rw-verify.yaml, andrw-verify-offline.yaml. They are defined once here and referenced below as (vuln-threshold block):
vuln-threshold-critical(optional, string, default: '0'): Maximum critical vulnerabilities allowed (0=none, -1=unlimited).vuln-threshold-high(optional, string, default: '0'): Maximum high vulnerabilities allowed (0=none, -1=unlimited).vuln-threshold-medium(optional, string, default: '0'): Maximum medium vulnerabilities allowed (0=none, -1=unlimited).vuln-threshold-low(optional, string, default: '0'): Maximum low vulnerabilities allowed (0=none, -1=unlimited).
subject-name(required, string, default: '${{ github.repository }}'): Subject name as it should appear in the attestation.github-token(optional, string, default: ''): The GitHub token set throughout the reusable workflow including the composite (build) action.
- No inputs for this action
There is no
subject-nameinput: the subject is always derived from${{ github.repository }}, so build, attest, and verify bind to the same subject — the registry-qualified name<registry>/<github.repository>(e.g.ghcr.io/liatrio/autogov-workflows).
registry(required, string, default: 'ghcr.io'): Container registry to push image.cert-identity(required, string): The certificate identity of the signer workflow used in the verify job. The workflow name should be rw-attest-image.yaml.autogov-version(optional, string, default:c78ba4d39b3e7a2fc752389f738aeddd53b8eba5, the v1.4.5 commit): The autogov release tag or full 40-character released commit SHA to use.release-image(optional, boolean, default: true): Whether to run the release-image job.octo-sts-read-scope/octo-sts-read-identity(optional, string, default: ''): octo-sts read pair threaded to the release job's autogov CLI download. Empty →github.token.octo-sts-release-scope/octo-sts-release-identity(optional, string, default: 'liatrio' / 'release-ops'): octo-sts write pair threaded to the release cut and asset upload. Seerw-release.yamlfor the branch ruleset bypass requirement.- (vuln-threshold block)
policy-data-overlay(optional, string, default: ''): Optional JSON forwarded to the nested verify workflow and merged over generated policy data. Usesource_review_thresholdsfor source-review settings; empty disables the overlay.
subject-path(required, string): Path to the artifact serving as the subject of the attestation.cert-identity(required, string): The certificate identity of the signer workflow used in the verify job. The workflow name should be rw-attest-blob.yaml.autogov-version(optional, string, default:c78ba4d39b3e7a2fc752389f738aeddd53b8eba5, the v1.4.5 commit): The autogov release tag or full 40-character released commit SHA to use.release-blob(optional, boolean, default: true): Whether to run the release-blob job.octo-sts-read-scope/octo-sts-read-identity(optional, string, default: ''): octo-sts read pair threaded to the release job's autogov CLI download. Empty →github.token.octo-sts-release-scope/octo-sts-release-identity(optional, string, default: 'liatrio' / 'release-ops'): octo-sts write pair threaded to the release cut and asset upload. Seerw-release.yamlfor the branch ruleset bypass requirement.- (vuln-threshold block)
subject-path(required, string): Path to the artifact serving as the subject of the attestation.cert-identity(required, string): The certificate identity of the signer workflow used in the verify job. The workflow name should be rw-attest-blob-offline.yaml.autogov-version(optional, string, default:c78ba4d39b3e7a2fc752389f738aeddd53b8eba5, the v1.4.5 commit): The autogov release tag or full 40-character released commit SHA to use.release-blob(optional, boolean, default: true): Whether to run the release-blob job.mutations-config(optional, string, default: ''): Path to the mutations config file (e.g. .autogov-release.yaml) passed through to the release-blob job. Leave empty to skip mutations.octo-sts-read-scope/octo-sts-read-identity(optional, string, default: ''): octo-sts read pair threaded to the release job's autogov CLI download. Empty →github.token.octo-sts-release-scope/octo-sts-release-identity(optional, string, default: 'liatrio' / 'release-ops'): octo-sts write pair threaded to the release cut and asset upload. Seerw-release.yamlfor the branch ruleset bypass requirement.- (vuln-threshold block)
policy-data-overlay(optional, string, default: ''): Optional JSON forwarded to the nested verify workflow and merged over generated policy data. Usesource_review_thresholdsfor source-review settings; empty disables the overlay.
subject-name(required, string): Subject name as it should appear in the attestation.registry(required, string, default: 'ghcr.io'): Container registry to push image.show-summary(optional, boolean, default: true): Whether to attach a list of generated attestations to the workflow run summary page.workflow-runner-label(optional, string, default: 'ubuntu-latest'): The label used for runner/OS selection.github-token(optional, string): The GitHub token set throughout the reusable workflow including the composite (build) action.autogov-version(optional, string, default:c78ba4d39b3e7a2fc752389f738aeddd53b8eba5, the v1.4.5 commit): The autogov release tag or full 40-character released commit SHA to use for predicate generation.
subject-path(required, string): Path to the artifact serving as the subject of the attestation.blob-artifact-name(optional, string, default: 'blob-build-artifact-high-perms'): The name of the artifact for the blob(s) built from the build-blob action.show-summary(optional, boolean, default: true): Whether to attach a list of generated attestations to the workflow run summary page.workflow-runner-label(optional, string, default: 'ubuntu-latest'): The label used for runner/OS selection.github-token(optional, string): The GitHub token set throughout the reusable workflow including the composite (build) action.autogov-version(optional, string, default:c78ba4d39b3e7a2fc752389f738aeddd53b8eba5, the v1.4.5 commit): The autogov release tag or full 40-character released commit SHA to use.
subject-path(required, string): Path to the artifact serving as the subject of the attestation.blob-artifact-name(optional, string, default: 'blob-build-artifact-low-perms'): The name of the artifact for the blob(s) built from the build-blob action.show-summary(optional, boolean, default: true): Whether to attach a list of generated attestations to the workflow run summary page.workflow-runner-label(optional, string, default: 'ubuntu-latest'): The label used for runner/OS selection.github-token(optional, string): The GitHub token set throughout the reusable workflow including the composite (build) action.autogov-version(optional, string, default:c78ba4d39b3e7a2fc752389f738aeddd53b8eba5, the v1.4.5 commit): The autogov release tag or full 40-character released commit SHA to use.
-
build-type(required, string): Specify the type of build: "image" or "blob". -
subject-name(optional, string, default: '${{ github.repository }}'): Subject name as it should appear in the attestation. Required unless subject-path is specified. -
image-digest(optional, string): The digest of the image that was built and pushed. Required for build-type "image". -
registry(optional, string, default: 'ghcr.io'): The container registry to use. -
blob-artifact-id(optional, string): The artifact-id of the build artifacts. Required for build-type "blob". -
cert-identity(required, string): The certificate identity of the signer workflow used in the verify job. The workflow name should be rw-attest-image.yaml for images, or rw-attest-blob.yaml for blob(s). -
github-token(optional, string, default: ''): The GitHub token set throughout the reusable workflow including the composite (build) action. -
workflow-runner-label(optional, string, default: 'ubuntu-latest'): The label of the workflow runner. -
autogov-version(optional, string, default:c78ba4d39b3e7a2fc752389f738aeddd53b8eba5, the v1.4.5 commit): The autogov release tag or full 40-character released commit SHA to use (input name retained for backwards compatibility). -
octo-sts-scope(optional, string, default: ''): octo-sts scope for cross-repo reads (autogov binary, policy bundle, cert-identities). When empty,github.tokenis used (suitable for public repos); when set, the octo-sts token is used and is required (verification fails closed if the exchange fails). -
octo-sts-identity(optional, string, default: ''): octo-sts identity. Required whenocto-sts-scopeis set. -
autogov-repo(optional, string, default: 'liatrio/autogov'): Repository to download the autogov CLI release from. -
policy-repo(optional, string, default: 'liatrio/autogov-policy-library'): Repository providing the OPA policy bundle and schemas (consumed asghrel://<policy-repo>?asset=...). -
cert-identities-repo(optional, string, default: 'liatrio/autogov-workflows'): Repository providing the cert-identities allowlist (cert-identities.json). When this is the called workflow repository, verification uses the matching immutable release asset; an explicit different repository uses its default branch. -
use-cert-identity-list(optional, boolean, default: '${{ github.repository != 'liatrio/autogov-workflows' }}'): Whether to use cert-identity-list for validation. -
(vuln-threshold block)
-
policy-data-overlay(optional, string, default: ''): Optional JSON merged over the generated vuln thresholds to enable per-repo gates such assource_review_thresholds/bypass_thresholds/code_scan_thresholds. Empty disables the overlay. -
allow-failed-vsa(optional, boolean, default: false): When false, a FAILED policy result fails this job after the FAILED VSA is attested and uploaded (record preserved, release blocked); set true to keep the advisory (non-gating) behavior. (Also accepted by therw-build-image/rw-build-blob/rw-build-blob-offlineworkflows.)
For example, require source-review evidence with at least two qualifying approvals:
policy-data-overlay: '{"source_review_thresholds":{"require_source_review":true,"min_approvals":2}}'Use source_review_thresholds as the external data key. source_review_config is a Rego package name; setting its rule names in external data causes a policy compilation conflict. The overlay must be a JSON object and cannot set vuln_thresholds; use the validated vuln-threshold-* inputs for those values.
blob-artifact-id(required, string): The artifact-id of the build artifacts.attest-build-attestation-artifact-id(optional, string): The artifact-id of the build provenance attestation artifact.attest-metadata-attestation-artifact-id(optional, string): The artifact-id of the custom metadata attestation artifact.attest-sbom-attestation-artifact-id(optional, string): The artifact-id of the SBOM attestation artifact.attest-dependency-scan-attestation-artifact-id(optional, string): The artifact-id of the dependency scan attestation artifact.cert-identity(required, string): The certificate identity of the signer workflow used in the verify job. The workflow name should be rw-attest-blob-offline.yaml.github-token(optional, string, default: ''): The GitHub token set throughout the reusable workflow including the composite (build) action.workflow-runner-label(optional, string, default: 'ubuntu-latest'): The label of the workflow runner.autogov-version(optional, string, default:c78ba4d39b3e7a2fc752389f738aeddd53b8eba5, the v1.4.5 commit): The autogov release tag or full 40-character released commit SHA to use (input name retained for backwards compatibility).cert-identities-repo(optional, string, default: 'liatrio/autogov-workflows'): Repository providing the cert-identities allowlist (cert-identities.json). When this is the called workflow repository, verification uses the matching immutable release asset; an explicit different repository uses its default branch.use-cert-identity-list(optional, boolean, default: '${{ github.repository != 'liatrio/autogov-workflows' }}'): Whether to use cert-identity-list (multi-signer allowlist) for validation. Mirrors the online verify default.- (vuln-threshold block)
policy-data-overlay(optional, string, default: ''): Optional JSON merged over the generated vuln thresholds to enable per-repo gates such assource_review_thresholds/bypass_thresholds/code_scan_thresholds. Empty disables the overlay.allow-failed-vsa(optional, boolean, default: false): When false, a FAILED policy result fails this job after the FAILED VSA is attested and uploaded (record preserved, release blocked); set true to keep the advisory (non-gating) behavior. (Also accepted by therw-build-image/rw-build-blob/rw-build-blob-offlineworkflows.)
branch(optional, string, default: 'main'): Branch to cut the release from.mutations-config(optional, string, default: ''): Path to the mutations config file (e.g. .autogov-release.yaml).dry-run(optional, boolean, default: false): Run in dry-run mode (no commits, tags, or releases created).autogov-version(optional, string, default:c78ba4d39b3e7a2fc752389f738aeddd53b8eba5, the v1.4.5 commit): The autogov release tag or full 40-character released commit SHA to download and use. A SHA is accepted only when exactly one immutable published release tag resolves to that commit; explicit tag overrides remain supported, including published 40-hex tag names.vsa-artifact-id(optional, string, default: ''): Comma-delimited artifact IDs of VSAs to upload as release assets.expected-vsa-artifact-count(optional, number, default: 0): Required normalized VSA ID count. Combined callers set this to their number of required VSA-producing builds so a missing output fails before download; zero preserves optional/single-path behavior.blob-artifact-id(optional, string, default: ''): Comma-delimited artifact IDs of blobs to download and publish as release assets.expected-blob-artifact-count(optional, number, default: 0): Required normalized blob ID count. Set this when blob release assets are required; zero leaves the class optional.workflow-runner-label(optional, string, default: 'ubuntu-latest'): The label of the workflow runner.github-token(optional, string, default: ''): GitHub token for the binary download whenocto-sts-read-scopeis empty. Leave empty to usegithub.token(suitable for public repos).octo-sts-read-scope(optional, string, default: ''): octo-sts scope for the autogov CLI download (read). When empty,github.tokenis used (suitable for public repos).octo-sts-read-identity(optional, string, default: ''): octo-sts read identity. Required whenocto-sts-read-scopeis set.octo-sts-release-scope(optional, string, default: 'liatrio'): octo-sts scope for the write path (release cut commit/tag and asset upload). External orgs with a protected main override this with their own scope whose octo-sts app is on the branch ruleset bypass list.octo-sts-release-identity(optional, string, default: 'release-ops'): octo-sts write identity for the release cut and asset upload.
Attestation artifact IDs — the following five outputs recur identically across the build/attest workflows. They are defined once here and referenced below as (attest-artifact-id block):
attest-build-attestation-artifact-id(string): The artifact-id of the build provenance attestation artifact.attest-metadata-attestation-artifact-id(string): The artifact-id of the custom metadata attestation artifact.attest-sbom-attestation-artifact-id(string): The artifact-id of the SBOM attestation artifact.attest-dependency-scan-attestation-artifact-id(string): The artifact-id of the dependency scan attestation artifact.attest-source-review-attestation-artifact-id(string): The artifact-id of the source-review (PR-approval) attestation artifact.
image-digest(string): The image digest of the image that was built from the build-image job.subject-name-sanitized(string): The sanitized (lowercase) subject name for OCI compliance. Use this value when referencing OCI image paths to ensure compliance with OCI naming requirements.
- No outputs for this action
- (attest-artifact-id block)
image-digest(string): The image digest of the image that was built.vsa-artifact-id(string): The artifact ID of the uploaded VSA.
- (attest-artifact-id block)
blob-artifact-id(string): The artifact-id of the blob artifact(s).vsa-artifact-id(string): The artifact ID of the uploaded VSA.
- (attest-artifact-id block) — except
attest-source-review-attestation-artifact-id, which this workflow does not emit. blob-artifact-id(string): The artifact-id of the blob artifact(s).vsa-artifact-id(string): The artifact ID of the uploaded VSA.
image-digest(string): The image digest of the image that was built from the build-image job.- (attest-artifact-id block)
blob-artifact-id(string): The artifact-id of the blob artifact(s).- (attest-artifact-id block)
blob-artifact-id(string): The artifact-id of the blob artifact(s).- (attest-artifact-id block) — except
attest-source-review-attestation-artifact-id, which this workflow does not emit.
vsa-artifact-id(string): The artifact ID of the uploaded VSA.
vsa-artifact-id(string): The artifact-id of the VSA attestation artifact.
version(string): The version of the release that was cut.tag(string): The tag created for the release.commit-sha(string): The SHA of the release commit.commit-verified(string): Whether the release commit is verified.
An end-to-end pipeline wires the build (entrypoint), attest, verify, and release jobs together. The entrypoint rw-build-* workflows call the matching rw-attest-* internally; the snippets below show how a caller chains the jobs explicitly:
When one repository builds an image and a blob in the same run, disable both reusable workflows' child publishers and add one caller-owned release job. Keep a normal needs dependency on every required build: do not add always() or another permissive job condition, because a failed, cancelled, or skipped required build must prevent publication.
| Build result | Caller release result |
|---|---|
| Image and blob succeed | One release cut receives both VSAs and every blob release asset. |
| Either build fails or is cancelled | The release job does not run. |
| Either required build is skipped | The release job does not run. |
| A supplied optional artifact-ID list is empty, whitespace, or comma-only | That artifact class is omitted. |
jobs:
build-image:
uses: liatrio/autogov-workflows/.github/workflows/rw-build-image.yaml@<released_commit_sha>
secrets: inherit
with:
release-image: false
# ...image inputs...
build-blob-offline:
uses: liatrio/autogov-workflows/.github/workflows/rw-build-blob-offline.yaml@<released_commit_sha>
secrets: inherit
with:
release-blob: false
# ...offline blob inputs...
release:
permissions:
contents: write
id-token: write
actions: read
needs: [build-image, build-blob-offline]
uses: liatrio/autogov-workflows/.github/workflows/rw-release.yaml@<released_commit_sha>
secrets: inherit
with:
mutations-config: .autogov-release.yaml
vsa-artifact-id: >-
${{ needs.build-image.outputs.vsa-artifact-id }},
${{ needs.build-blob-offline.outputs.vsa-artifact-id }}
expected-vsa-artifact-count: 2
blob-artifact-id: ${{ needs.build-blob-offline.outputs.blob-artifact-id }}
expected-blob-artifact-count: 1Use the same pattern with rw-build-blob.yaml: set release-blob: false, replace build-blob-offline in needs and output references with the online blob job, and keep mutations-config on the caller-owned rw-release.yaml job. Comma-delimited artifact-ID inputs may contain multiple IDs; rw-release.yaml trims whitespace and removes empty entries before checking the expected counts and downloading each artifact into an isolated source directory. For multiple artifacts, each source ID is the stable artifact-name directory created by actions/download-artifact (for example, verification-summary-attestation-high-perms and verification-summary-attestation-low-perms); the CLI sanitizes that ID when constructing the VSA release name, so renaming an uploaded artifact also changes its released VSA name. Single-artifact classes retain the fixed vsa and blob source IDs. The CLI recursively resolves those directories, omits empty files, namespaces every source VSA, and rejects final-name collisions before release mutation. Existing single-path callers may keep the default release-image: true or release-blob: true behavior and leave both expected counts at zero.
Migration note: do not resume a draft created by an older workflow after switching to source-namespaced release assets. Remove that unpublished draft and rerun the release so the new CLI can preflight and upload one internally consistent asset set.
jobs:
# 1. build + attest (use rw-build-image / rw-build-blob / rw-build-blob-offline)
attest-image:
permissions:
id-token: write
attestations: write
packages: write
contents: write
uses: liatrio/autogov-workflows/.github/workflows/rw-attest-image.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release
secrets: inherit
with:
subject-name: ${{ github.repository }}
registry: ghcr.io
# 2. verify -> emits the gating VSA
verify-image:
permissions:
id-token: write
attestations: read
packages: read
needs: [attest-image]
uses: liatrio/autogov-workflows/.github/workflows/rw-verify.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release
secrets: inherit
with:
build-type: image
image-digest: ${{ needs.attest-image.outputs.image-digest }}
cert-identity: https://github.com/liatrio/autogov-workflows/.github/workflows/rw-attest-image.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release
# 3. release -> cuts the release attaching the VSA + blob assets
release-image:
permissions:
contents: write
id-token: write
actions: read
needs: [verify-image, attest-image]
uses: liatrio/autogov-workflows/.github/workflows/rw-release.yaml@<commit_sha> # <semver> / a commit SHA from an official autogov-workflows release
secrets: inherit
with:
mutations-config: .autogov-release.yaml
vsa-artifact-id: ${{ needs.verify-image.outputs.vsa-artifact-id }}For blob builds, swap rw-attest-image.yaml → rw-attest-blob.yaml (or rw-attest-blob-offline.yaml), set build-type: blob, and pass blob-artifact-id: ${{ needs.attest-blob.outputs.blob-artifact-id }} (plus the attest-*-attestation-artifact-id outputs to rw-verify-offline.yaml for the offline path) instead of image-digest.
To update files as part of a release (e.g. version bumping a Dockerfile, manifests, or config), add a .autogov-release.yaml mutations file to your repository root and pass it to the release workflow via mutations-config (see the caller example above). On release, autogov release cut applies the mutations, commits the changed files in the chore(release) commit, and tags the new version.
Each mutation has a path, a type, and a field; ${version} expands to the new release version. Supported types:
jsonPath— set a field in a JSON file (fieldis the key, e.g.version).yamlPath— set a field in a YAML file (fieldis the key, e.g.versionorappVersion).regexReplace— replace a regex match (fieldis the pattern,replaceis the replacement template).exec— run a command (fieldis the command).
A mutations file can combine types — for example, a regexReplace version bump and an exec regeneration:
mutations:
- path: Dockerfile
type: regexReplace
field: 'ENV VERSION="[^"]*"'
replace: 'ENV VERSION="${version}"'
- path: cert-identities.json
type: exec
field: './scripts/update-cert-identities.sh ${version}'For multiple files, add a rule per file, or use an exec mutation (e.g. find . -name '*.yaml' -exec sed -i 's/version: .*/version: ${version}/' {} \;).
-
Permission Denied: Ensure that your PAT and respective workflows have the necessary access.
-
Workflow Fails to Trigger: Check that you are using one of the supported event types:
create,release,push, orworkflow_dispatch. -
Attestation Verification Fails: Ensure that the
cert-identityand other inputs are correctly specified. Verify that the workflow is running on GitHub-hosted runners.
To debug GitHub environment variables (owner, repository, etc.), dump the contexts in a step. A printenv | grep '^GITHUB_' or echoing toJson(github) / toJson(runner) into an env: block then echo-ing it is usually enough; the inputs context (toJson(inputs)) is the one to dump when checking workflow inputs.
If you encounter any issues not covered here, please open an issue on our GitHub repository.
- docs/verification-cosign.md — verifying attestations with Sigstore cosign (prerequisites + image paths).
- docs/oci-attestation-internals.md — OCI v1.1 Referrers API and inspecting attestation manifests with ORAS.
- docs/slsa-workflow-inputs.md — how autogov records workflow inputs to satisfy SLSA build-level requirements.
- Sigstore Rekor — transparency log of signed metadata.
- Sigstore Fulcio — keyless certificate authority for OIDC identities.
- Sigstore Cosign — signing and verifying images and artifacts.
- Why is Github Artifact Attestations Considered SLSA Build L2+ and not SLSA Build L3?
- Trusted Builder and Provenance Generator Specifications
- Hardening Requirements
- Best SDLC Practices
- Build Your Own Builder (BYOB) Framework
- Provenance Build Definition
- Provenance Model/Schema



