Skip to content

Supply-Chain Attestations

This page is for the first time you’re asked to sign an artifact and you’re not sure what any of the acronyms mean. It covers four things chant attaches to a build, in plain language — for the sign/verify/vuln-gate walkthroughs and capability reference, see Attestation Reference.

TermWhat it actually is
SBOM (Software Bill of Materials)A list of every package/library in the artifact — name, version, license. Answers “what’s in this thing,” the same way a food label answers “what’s in this can.”
SLSA provenanceA signed statement of where the artifact came from and how it was built — source commit, builder identity, build timestamp. Answers “who built this and from what,” not “what’s in it.”
SignatureCryptographic proof that a specific identity (a person or a CI workflow) produced this exact artifact, plus a public record that the signature happened. Answers “who vouches for these exact bytes.”
VEX / CVEA CVE is a known vulnerability in some package. VEX is a statement about whether a given CVE actually affects this artifact the way it’s used. Answers “of everything the SBOM lists, what actually matters.”
KEVCISA’s catalog of vulnerabilities someone has actually been caught exploiting in the wild. Not “could be bad” — “is being used against people right now.”
EPSSA 0-to-1 score estimating the chance a given CVE gets exploited in the next 30 days. A high-severity bug nobody knows how to exploit scores low.

An SBOM tells you what’s inside. Provenance tells you where it came from. A signature tells you who’s vouching for it. None of the three answers the other two — that’s why chant attaches all three separately, to the same artifact, rather than treating any one as a stand-in for the others.

See Attestation Reference for what each of generate-sbom/sign/attest-provenance/verify/vuln-gate needs installed and the full composition walkthrough.

The obvious way to sign something is a private key: generate a keypair, sign with the private half, publish the public half for verification. That works, but it hands a beginner a new hazard — a key file that can leak, that can be lost (taking your ability to sign with it), and that someone has to remember to rotate.

Keyless signing removes the key file entirely. Instead of a long-lived secret, cosign asks an OIDC identity provider you already use — your CI system’s built-in token, or a browser login — “prove who you are,” and gets back a short-lived certificate good for a few minutes. It signs with that, and logs the signature to a public transparency log (Rekor) so anyone can later confirm a signature really was issued at that time for that identity. The certificate expires almost immediately; there’s nothing left to steal afterward.

This is why chant’s sign capability defaults to keyless with no configuration: there’s no key to generate before your first signature, and nothing to rotate afterward. Key-based signing still exists as an explicit opt-in (key config) for teams with an existing KMS policy, but it is never what a new project reaches for first.

The one thing you must set: an OIDC identity

Section titled “The one thing you must set: an OIDC identity”

Signing needs no setup — cosign detects ambient OIDC automatically in GitHub Actions, GitLab CI, and most CI providers, and falls back to an interactive browser login locally. Verifying does need one thing from you: telling verify which identity is allowed to have signed.

Without this, “verify” would only mean “some signature exists” — which any keyless signer, including an attacker who can also complete an OIDC login, can produce. Naming the expected issuer and identity is what turns “a signature exists” into “the identity I trust signed this.”

Set it in two places that must agree:

chant.config.ts
export default {
signing: {
oidcIssuer: "https://token.actions.githubusercontent.com",
identity: "https://github.com/my-org/my-repo/.github/workflows/release.yml@refs/heads/main",
},
};
a component's verify step
import { verify } from "@intentius/chant/components/verbs/verify";
await verify.run(ctx, {
imageRef: "@Sign.imageRef",
policy: {
expectedIssuer: "https://token.actions.githubusercontent.com",
expectedIdentity: "https://github.com/my-org/my-repo/.github/workflows/release.yml@refs/heads/main",
},
});

resolveSigningDefaults() (packages/core/src/config.ts) reads the signing block from chant.config.ts so a component’s verify step doesn’t have to repeat the issuer/identity literally — it resolves project-wide defaults a per-call policy can still override.

For GitHub Actions and GitLab CI, oidcIssuer is the provider’s fixed token endpoint and identity is your own workflow’s ref — both values you can read straight off the provider’s own OIDC docs, not something chant invents. Locally, signing falls back to an interactive Sigstore login (a browser tab), and identity becomes the email cosign’s certificate records for that login.

identityIsRegexp: true treats identity as a pattern instead of a literal match, for teams whose workflow ref varies by branch or tag and don’t want to enumerate every one.

A tag like myapp:latest is a pointer that can move — the next push retargets it to different bytes without changing the tag. Signing a tag would only ever mean “something with this name was signed at some point,” which says nothing about what a docker pull actually resolves to by the time someone runs it.

A digest (myapp@sha256:...) is the artifact’s own content hash. It cannot be reassigned to different bytes; a signature on a digest is a signature on those exact bytes, forever.

sign and attest-provenance enforce this structurally: both throw SignTargetNotDigestError before ever invoking cosign if the reference isn’t digest-qualified. In practice you never construct that digest by hand — it comes from a prior publish step’s output ("@Publish.uri", itself repo@sha256:...; see Build Archive).

See Attestation Reference for the first-signing walkthrough and the VEX/CVE and KEV/EPSS gating fields.

  • Attestation Reference — the prerequisites table, the SBOM/sign/verify/vuln-gate walkthroughs, and the KEV/EPSS gating fields.
  • Build Archive — how the SBOM is generated and where the digest sign/verify operate on comes from.
  • Observability — the build ledger’s referrer discovery, which can surface a digest’s signature/provenance/SBOM referrers after the fact.
  • Orchestration — how a thrown VerificationFailedError halts a composition before apply runs.