Skip to content

About

Reusable GitHub Actions workflows for autogov / build, attest (SLSA build provenance, SBOM, VSA), and verify artifacts using GitHub Artifact Attestations.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

2 watching

Forks

Reusable Workflows using GitHub Artifact Attestations

lint OpenSSF Scorecard Release License: Apache 2.0

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

  1. Configure Access: Ensure you have the necessary permissions and tokens configured in your remote caller repository described in the access section below.

  2. 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 via autogov release plan; adjust the Build and push step 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 as subject-path (for an npm package, that's the npm/yarn pack output, e.g. liatrio-simple-greeter-1.0.0.tgz).
  3. 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
  1. Run the Workflow: Trigger the workflow using one of the supported event types:

  2. Check Results: Review the workflow logs and the uploaded Verification Summary Attestation (VSA) artifact. The autogov CLI performs signature verification, OPA/Rego policy evaluation, and VSA generation in a single step. A passing run produces vsa-PASSED.json. A failing policy evaluation produces vsa-FAILED.json; the FAILED VSA is still attested and uploaded (the record is preserved), and the job then fails and the release is skipped — unless allow-failed-vsa: true is set, which keeps the run advisory. The gate is default-deny: it ships only on an explicit PASSED result, so any non-PASSED outcome (FAILED, or a missing/unknown VSA) also blocks the release.

Attesting the Workflow Files

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 bundle autogov.attestations.intoto.jsonl (covering the rw-*.yaml reusables), a source-namespaced VSA proving those attestations verified against policy, and autogov-workflows.intoto.jsonl — the single build-provenance statement for OpenSSF Scorecard's release-provenance detection. Release VSAs are always named vsa-<source>-<result>.json, including single-source releases (vsa-vsa-PASSED.json); combined online/offline verification artifacts produce names such as vsa-verification-summary-attestation-high-perms-PASSED.json and vsa-verification-summary-attestation-low-perms-PASSED.json. The rw-*.yaml reusables themselves are not attached as release assets (they're consumed via uses: ...@<ref> and attested by digest in the bundle); rw-build-blob/rw-build-blob-offline expose release-blob-asset (default true) to control that, and consumer releases built through rw-build-image / rw-build-blob attach 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-workflows

This 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.

Paving the Path

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 only way to interact with a reusable workflow is through the input parameters it exposes to the calling workflow."

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.

Achieving SLSA Build Levels Using Reusable Workflows

Verification / cert-identity

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).

Certificate Identities

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.

Verification Using Cosign

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.

Verification Using ORAS

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 format
  • https://slsa.dev/provenance/v1: SLSA provenance attestation
  • https://autogov.dev/attestation/metadata/v1: Metadata attestation
  • https://cyclonedx.org/bom: SBOM attestation
  • https://in-toto.io/attestation/vulns/v0.2: Vulnerabilities attestation
  • https://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.

L3: Isolation of Build from Attest

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.

Job Isolation

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: read

Otherwise (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: write

Note: 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.

L2: Ensure a Trusted Build Environment

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 1

Build 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.

L1: Documented Build Parameters

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: false

Workflow Inputs and SLSA

SLSA'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.

Why No Pull Request?

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.

Usage

Tools Used

The OCI Design

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.

Recent Tagged Image Versions

Additional Digest Example

Limiting Inputs by Wrapping Reusable Workflow Calls

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.

Access

Workflow Access

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.

Repository Access

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-workflows

For 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: write

More information about octo-sts can be found in the octo-sts app and the octo-sts/action.

Bring your own auth: rw-verify.yaml is consumer-configurable — set octo-sts-scope / octo-sts-identity (and, for cross-repo reads, autogov-repo / policy-repo / cert-identities-repo) to your own org and identities; leave octo-sts-scope empty to use github.token (the default, suitable for public repos). The scope: liatrio / identity: autogov-infra values shown in the other octo-sts examples are still liatrio-org-specific and hardcoded inside rw-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-identity default to liatrio / release-ops so 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.token cannot 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) set octo-sts-release-scope / octo-sts-release-identity to a trust policy that issues contents: write for that App on the release branch. These same four inputs are threaded through rw-build-image.yaml / rw-build-blob.yaml / rw-build-blob-offline.yaml to the nested rw-release call.

Setup AutoGov

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 version

autogov-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.

Inputs

Vulnerability thresholds (the four vuln-threshold-* inputs) recur identically on rw-build-image.yaml, rw-build-blob.yaml, rw-build-blob-offline.yaml, rw-verify.yaml, and rw-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).

.github/actions/build-image/action.yaml

  • 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.

.github/actions/build-blob/action.yaml

  • No inputs for this action

.github/workflows/rw-build-image.yaml

There is no subject-name input: 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. See rw-release.yaml for 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. Use source_review_thresholds for source-review settings; empty disables the overlay.

.github/workflows/rw-build-blob.yaml

  • 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. See rw-release.yaml for the branch ruleset bypass requirement.
  • (vuln-threshold block)

.github/workflows/rw-build-blob-offline.yaml

  • 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. See rw-release.yaml for 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. Use source_review_thresholds for source-review settings; empty disables the overlay.

.github/workflows/rw-attest-image.yaml

  • 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.

.github/workflows/rw-attest-blob.yaml

  • 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.

.github/workflows/rw-attest-blob-offline.yaml

  • 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.

.github/workflows/rw-verify.yaml

  • 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.token is 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 when octo-sts-scope is 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 as ghrel://<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 as source_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 the rw-build-image / rw-build-blob / rw-build-blob-offline workflows.)

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.

.github/workflows/rw-verify-offline.yaml

  • 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 as source_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 the rw-build-image / rw-build-blob / rw-build-blob-offline workflows.)

.github/workflows/rw-release.yaml

  • 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 when octo-sts-read-scope is empty. Leave empty to use github.token (suitable for public repos).
  • octo-sts-read-scope (optional, string, default: ''): octo-sts scope for the autogov CLI download (read). When empty, github.token is used (suitable for public repos).
  • octo-sts-read-identity (optional, string, default: ''): octo-sts read identity. Required when octo-sts-read-scope is 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.

Outputs

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.

.github/actions/build-image/action.yaml

  • 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.

.github/actions/build-blob/action.yaml

  • No outputs for this action

.github/workflows/rw-build-image.yaml

  • (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.

.github/workflows/rw-build-blob.yaml

  • (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.

.github/workflows/rw-build-blob-offline.yaml

  • (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.

.github/workflows/rw-attest-image.yaml

  • image-digest (string): The image digest of the image that was built from the build-image job.
  • (attest-artifact-id block)

.github/workflows/rw-attest-blob.yaml

  • blob-artifact-id (string): The artifact-id of the blob artifact(s).
  • (attest-artifact-id block)

.github/workflows/rw-attest-blob-offline.yaml

  • 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.

.github/workflows/rw-verify.yaml

  • vsa-artifact-id (string): The artifact ID of the uploaded VSA.

.github/workflows/rw-verify-offline.yaml

  • vsa-artifact-id (string): The artifact-id of the VSA attestation artifact.

.github/workflows/rw-release.yaml

  • 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.

Example Workflow Snippets

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:

Combined image and blob releases

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: 1

Use 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.

Explicit build, attest, verify, and release

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.

Automating Version Updates

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 (field is the key, e.g. version).
  • yamlPath — set a field in a YAML file (field is the key, e.g. version or appVersion).
  • regexReplace — replace a regex match (field is the pattern, replace is the replacement template).
  • exec — run a command (field is 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}/' {} \;).

Troubleshooting

Common Issues

  1. Permission Denied: Ensure that your PAT and respective workflows have the necessary access.

  2. Workflow Fails to Trigger: Check that you are using one of the supported event types: create, release, push, or workflow_dispatch.

  3. Attestation Verification Fails: Ensure that the cert-identity and 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.

Getting Help

If you encounter any issues not covered here, please open an issue on our GitHub repository.

Additional Resources

About

Reusable GitHub Actions workflows for autogov / build, attest (SLSA build provenance, SBOM, VSA), and verify artifacts using GitHub Artifact Attestations.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages