Language Runtimes · Packaging and Distribution

Provenance Attestations and the Trust Boundary of a Package Install

What an in-toto attestation binds together, how transparency logs change the meaning of a verified install, and which attacks remain outside the model.

The previous mechanisms in this cluster answer narrow questions. A hash says the bytes are the bytes. A signature says a key holder endorsed them. A lockfile says a resolver chose them. None of those answers says where the artifact came from, which is the question that decides whether installing it is reasonable.

Provenance is that answer stated as a signed claim: this artifact was produced by this build, from this source, by this builder, using these inputs. The claim is useful only if it is bound to the artifact and if the verifier has a policy for judging the claimant.

The attestation as a signed predicate over subjects

The in-toto framework supplies the vocabulary. An attestation is a statement with three parts: a subject, which is a set of artifacts identified by digest; a predicate type, which names the kind of claim being made; and a predicate, which carries the claim’s fields. The whole statement is signed by whoever is making it.

The subject list is what binds the claim to bytes. Because a subject is named by digest rather than by filename or version, an attestation about a particular artifact cannot be replayed onto a different artifact: changing the bytes changes the subject, and the signature no longer covers the thing being installed. The digest machinery is the same as for any other artifact check, covered in Content Addressing and What Artifact Hashes Actually Prove, and the provenance claim adds only the question of who expected those bytes.

Predicate types are what make attestations composable rather than ad hoc. A provenance predicate describes a build: the builder’s identity, the source repository and commit, the build recipe, the materials consumed, and the invocation parameters. A test-result predicate describes a test run. A vulnerability-scan predicate describes what a scanner concluded about the subject’s contents. Each is a separate claim, and a verifier can require some, ignore others, and apply different policies to each.

SLSA’s specification organises these claims into levels, and the useful part is the ladder’s direction. Level 1 is provenance that exists. Level 2 is provenance generated by a hosted build service rather than by the developer’s machine. Level 3 is provenance that is non-forgeable, meaning the build service isolates builds so that one build cannot influence another’s provenance. The progression is about who can lie: a developer can fabricate a level 1 attestation about anything, while a level 3 attestation must be signed by infrastructure the developer does not control.

What a signature over a statement proves

Verifying an attestation involves three separate checks, and a system that performs only the first is not verifying provenance.

The signature check confirms that a key holder produced the statement. With Ed25519, defined in RFC 8032, the check is cheap and the signature is 64 bytes, which is why it is the common choice for attestations.

The binding check confirms that the subject digests match the artifact being installed. This is the check that connects the claim to bytes, and it fails if the artifact was rebuilt or replaced after the attestation was signed.

The identity check confirms that the key belongs to an entity the policy accepts for this package. A signature by any valid key is not enough; the policy must say which identities are authorised, and that statement is external to the attestation.

Short-lived certificates complicate the third check usefully. A signing key can be bound to an identity through a certificate issued by an OIDC-based authority after the holder proves control of an account. The certificate lives for minutes, the signature is timestamped, and verification requires a transparency log entry showing the certificate was issued and published. The consequence is that verification no longer needs a preconfigured list of long-lived public keys; it needs a policy about which identities are acceptable, plus evidence that the key existed at signing time and was not revoked.

The transparency log is the load-bearing part. RFC 6962 established the structure for certificates: an append-only Merkle tree whose entries can be proven included by a short path, and whose head can be proven consistent with earlier heads. Applied to signing events, it means a signer cannot issue a valid-looking attestation for one consumer and deny it to another, because every issuance is recorded and any observer can check that the log only grew.

Policy is where verification actually happens

The difficult part of provenance is not cryptography. It is the policy decision that an attestation is acceptable.

A policy for a build artifact typically requires: the subject digest matches, the builder identity is on an allowlist, the source repository matches the package’s declared repository, the source commit is reachable from the default branch, and the build recipe is one the project recognises. Each condition is checkable against the attestation’s fields, and each can fail independently.

The source-commit condition is the one that catches real attacks. An attacker who gains publish rights to a registry can upload an artifact with valid provenance built from a fork or from a branch containing malicious changes. The provenance is genuine and the artifact is malicious. Requiring that the commit be an ancestor of the protected branch’s head closes that path, and it is exactly the check that a digest-only or signature-only verification cannot perform.

The recipe condition is subtler. Two builds from the same commit can differ because the build recipe differs: different compiler flags, a modified build script, a dependency resolved to a different version. Provenance that records the recipe lets a verifier compare it against a known-good template, and it also exposes the fact that a rebuild is not reproducible unless the recipe matches. The recipe is typically a digest of the build definition file rather than the file itself, which keeps attestations small. An attestation therefore describes an environment the builder controlled, which is the same class of pinning that Prefix-Based Environments and Why Activation Is Only Path Surgery attempts at the level of an installed package set.

A policy engine that evaluates these conditions over a bundle of attestations is the natural shape. The predicate’s field names differ between versions of the provenance specification, so the sketch below accesses them through named helpers rather than literal keys:

def accept(bundle, artifact_digest, policy):
    for att in bundle:
        stmt = att["statement"]
        if artifact_digest not in [s["digest"]["sha256"] for s in stmt["subject"]]:
            continue                                  # attestation is about other bytes
        pred = stmt["predicate"]
        if builder_id(pred) not in policy["builders"]:
            continue
        if source_repo(pred) != policy["repo"]:
            continue
        if not reachable(source_commit(pred), policy["branch"]):
            continue
        return True
    return False

reachable is a graph query against the source host, not a signature check, which is the point: the trust decision spans systems, and the attestation is only one input to it.

The attacks the model does not cover

Provenance answers “was this built from this source by this builder”. It does not answer “is this source safe”.

A malicious commit that lands in the default branch through a compromised maintainer account produces a fully valid attestation, and the source-commit check passes because the commit really is on the branch. Branch protection and review are the controls for that path, and they sit outside the attestation.

A compromised builder produces valid attestations for whatever it builds. Level 3 isolation reduces the chance that one build can influence another, but it does not prevent a builder operator from building something else. The verifier’s builder allowlist is only as good as its knowledge of which builders are trustworthy.

A dependency of the package is itself malicious. Provenance covers the artifact’s direct build inputs, and if those inputs include a compromised library, the attestation is accurate and the artifact is dangerous. The coverage is one level deep unless every dependency’s attestation is also verified, which is a policy decision with real cost: verifying transitively requires attestations for the whole graph, and many packages do not publish them.

Availability is a separate axis that provenance ignores. An attacker who can block access to the transparency log or the attestation store can cause verification to fail, and a client that fails open has no protection at all. The TUF specification exists largely for this problem, defining a metadata structure with role delegation, version numbers, and expiry so that an attacker cannot serve an old but validly signed metadata set. Rollback and freeze attacks are TUF’s subject matter, and they are orthogonal to provenance: one is about the freshness and completeness of metadata, the other about the origin of an artifact.

The residual is that installing a package is a decision to execute code with the installer’s privileges. Provenance narrows the set of explanations for how that code came to exist. It does not reduce the privilege, and the mechanisms that do are the runtime isolation controls, which are outside this cluster’s scope.

What to record, and at what cost

The practical question for a project is which attestations to produce and which to require. Producing them costs a build-system integration and a signing identity; requiring them costs a policy engine and a fallback path for packages that lack them.

The useful minimum is a provenance attestation per artifact, generated by the build service, with the source commit and recipe recorded, published alongside the artifact rather than in a separate store, and signed with a short-lived identity recorded in a transparency log. That combination gives a verifier everything needed for the checks above without requiring a pre-shared key distribution problem.

The minimum for a consumer is to verify the binding and the identity, and to treat an absent attestation as a policy failure for packages where the risk warrants it. Treating absence as success converts the whole apparatus into documentation.

The mechanisms in this cluster form a chain in which each link assumes the previous ones. The hash identifies bytes; the lockfile records a choice; the index supplies metadata; the environment isolates the result; the attestation explains the origin. A break anywhere in the chain leaves the later links technically correct and practically meaningless, which is why the weakest link, not the strongest, determines what an install actually guarantees.

References

  1. in-toto: A framework to secure the integrity of software supply chains — in-toto project
  2. SLSA: Supply-chain Levels for Software Artifacts — Open Source Security Foundation
  3. The Update Framework Specification — CNCF / TUF project
  4. RFC 6962: Certificate Transparency — IETF
  5. RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA) — IETF