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),vsa-PASSED.jsonproving those attestations verified against policy, andautogov-workflows.intoto.jsonl— the single build-provenance statement for OpenSSF Scorecard's release-provenance detection. 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.
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.
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: 'v1.0.4'): The autogov version 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: 'v1.0.4'): The autogov version 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: 'v1.0.4'): The autogov version 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: 'v1.0.4'): The autogov version 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: 'v1.0.4'): The autogov version 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: 'v1.0.4'): The autogov version 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: 'v1.0.4'): The autogov version 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).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_config/bypass_config/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.)
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: 'v1.0.4'): The autogov version 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).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_config/bypass_config/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: 'v1.0.4'): The autogov release version to download and use.vsa-artifact-id(optional, string, default: ''): The artifact ID of the VSA to upload as a release asset.blob-artifact-id(optional, string, default: ''): Artifact ID of the blob to download and publish as a release asset.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:
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



