Index

Supply-Chain Security Begins with Who Can Change the Artifact

quote-tool.bin has a valid signature. Its provenance names the expected source revision, dependency lock, workflow digest, and builder. The signature verifies against the consumer’s trusted fixture key.

The program is still malicious.

In the SEC-01 fixture, the authorized builder changes the artifact after the tests pass and signs the resulting digest. Signature verification succeeds because the statement is authentic. Consumer policy rejects the artifact because its digest does not match the independently reproduced output.

That difference is the useful starting point for software supply-chain security. A control can prove exactly what it claims and still leave another route to the artifact open.

A lockfile constrains dependency resolution. A full commit hash selects build instructions. Provenance binds an artifact to claims about its origin. A signature authenticates those claims. None of them proves by itself that the consumed bytes are the reviewed program.

Begin with authority instead. Trace every identity and system which can change the artifact between review and execution. Then decide what evidence the next boundary must require.

The artifact has more than one author

Source code is only the first mutable input. The fixture retains 12 authority edges across seven stages:

StageMutable objectAuthority which can change itConsumption evidence
SourceRevision and review recordContributor, reviewers, repository administratorApproved revision, ancestry, and independent approvals
DependenciesLockfile and package bytesLockfile updater, package publisher, registryExpected lock digest and resolved integrity
InstructionsWorkflow and referenced actionsWorkflow author and action publisherExpected workflow and action digests
BuildArtifact bytesBuild platform and jobBuilder identity, declared inputs, and artifact digest
ProvenanceClaims about the buildProvenance issuer or signing identityTrusted identity, valid signature, and expected fields
PublicationRegistry object and mutable nameArtifact publisher and registryImmutable artifact digest and scoped namespace
ConsumptionSelected bytesResolver, verifier, and deployerPolicy approval before extraction or execution

This is not a list of products to buy. It is a change path. Each row needs an expected identity or digest, a verification point, a failure action, a recovery action, and a residual risk.

The final column matters because evidence must cross boundaries. Verifying an artifact in CI does not protect a consumer which later downloads a mutable tag. Reviewing a workflow does not protect a job which resolves a third-party action from a tag its publisher can move. Protecting a branch does not remove an administrator’s ability to weaken or bypass the rule.

The threat model therefore asks a concrete question at every transition: who can change the object which the next system is about to trust?

A commit identifies content, not how it was approved

The fixture creates a reviewed source revision, two independent approval identities, and an unauthorized revision outside the approved ancestry. The source verifier rejects the unauthorized revision and rejects a review record which reuses one identity for both required approvals.

The commit hash alone cannot make either decision. It identifies content and parents. It does not contain the repository’s protected-branch policy, reviewer independence, administrator authority, or evidence that those rules were enforced when the revision was accepted.

The SLSA 1.2 Source Track separates source roles and explicitly models administrators and two-party review. That model is useful here without claiming that this synthetic repository satisfies a SLSA level. The fixture does not reproduce a hosted source-control service or its administrative boundary.

This distinction changes what provenance must carry. As A Pull Request Is a Review Narrative argues, the revision and the evidence which made it acceptable are different artifacts. Consumer policy must know which repository and revision are allowed. If approval evidence matters, preserve it as evidence about the revision rather than pretending it lives inside the hash.

Frozen dependencies can reproduce a malicious decision

The clean build resolves dependency version 1.0.0. Making 1.1.0 available does not change that result while the lockfile remains unchanged. This is the property a frozen install should provide.

The negative control changes the reviewed lockfile to select a malicious 1.1.0. Installation remains repeatable. The same unwanted package is chosen each time.

npm documents that npm ci uses an existing lockfile and exits when it does not agree with package.json. Its lockfile documentation describes the exact dependency tree, resolved locations, and integrity data. Those are valuable controls. They do not establish whether a publisher was compromised, whether the package deserves its name, or whether the lockfile change received meaningful review.

The honest conclusion is narrower: a lockfile can make resolution repeatable and expose an unexpected dependency-graph change. It can also preserve a bad decision perfectly. Publisher trust, update authorization, install-time code, and runtime behavior remain separate questions.

An immutable workflow reference fixes bytes, not intent

Build instructions are executable dependencies. A movable action tag in the fixture resolves to clean code for one build and malicious code for the next. The workflow text has not changed, but the program granted build authority has.

Replacing the tag with an immutable digest closes that particular route. It proves which action bytes were selected. It does not prove those bytes are benign or deserve credentials.

GitHub’s Actions hardening guidance recommends pinning third-party actions to full commit SHAs and granting minimum permissions. Those are two controls because they answer different questions. The pin constrains selection. The permission boundary constrains what selected code can do.

The fixture’s build job can read source and write its own artifact. It receives no unnecessary secret and runs without network access. Those choices reduce the inputs it can fetch and the credentials it can leak. They do not prove that a hosted runner, image publisher, or authorized build program is safe.

An immutable malicious action remains malicious. A reviewed action can become dangerous when a later workflow grants it a publication token, signing identity, secret, or unrestricted egress. Record the selected bytes and the authority they receive.

Provenance answers origin questions, not safety questions

The fixture’s signed provenance binds the artifact name and SHA-256 digest to the exact source revision, workflow digest, dependency-lock digest, builder identity, invocation parameters, and declared environment.

Consumer verification rejects altered artifact bytes, altered provenance, an untrusted key, the wrong source revision, wrong workflow, wrong artifact name, missing provenance, and a revoked builder. These are useful failures. They turn “this came from CI” into fields a verifier can compare against policy.

SLSA provenance describes verifiable information about where, when, and how an artifact was produced. Sigstore’s verification documentation likewise requires the verifier to constrain the expected identity and bind verification to the artifact digest. A valid signature without an expected identity is only half a decision.

The malicious-authorized-builder case preserves the other half of the boundary. Its signature is valid. Its source and workflow claims are well-formed. Cryptography cannot tell that authorized build logic changed the output after tests. A separate expected-output policy rejects the different digest.

Reproducible output is useful evidence in this controlled example because the same declared inputs produce the same tiny binary. It is not universal proof that a remote builder was uncompromised. Many real builds are not reproducible, and two builds can share a compromised input. The independent comparison is a detective control with stated assumptions, not a verdict about intent or vulnerability.

GitHub makes the limitation unusually plain in its artifact-attestation documentation: generating an attestation has no security benefit until it is verified, and an attestation does not guarantee that an artifact is secure. Provenance improves the question from “do we trust CI?” to “do these bytes satisfy the origin and process claims this consumer requires?”

Publication and consumption need separate gates

The fixture publishes the approved artifact under both its immutable digest and a mutable name. CI verifies the approved digest. The mutable name is then moved to a substituted artifact.

A consumer which downloads by name and trusts the earlier CI result accepts a different object from the one CI checked. The fixed consumer selects or receives the artifact, calculates the digest, verifies that digest against the provenance subject, checks the expected source, workflow, and builder, and does so before extraction or execution.

Ordering is part of the control. Verifying an archive after an install hook, package script, or executable payload has run turns prevention into forensic evidence.

The publication token has a different boundary. A wildcard token can overwrite an unrelated namespace. A token scoped to quote-tool cannot. The narrow token can still replace artifacts inside quote-tool; least privilege reduces blast radius rather than preventing compromise.

The same precision belongs in recovery:

  • reject substituted bytes and retain the received digest;
  • revoke the publisher or builder identity which crossed its boundary;
  • restore the approved immutable object;
  • rebuild every artifact which may share the compromised input; and
  • identify consumers which downloaded before the rejection policy was fixed.

“Rotate credentials” is incomplete unless the affected authority, namespace, artifacts, and consumers are known.

Keep the controls separate enough to disagree

A supply-chain maturity score would hide the most important result in the fixture: several controls pass while the artifact remains unacceptable.

The lockfile is repeatable while selecting malicious bytes. The signature is valid while authenticating malicious output. The immutable action reference is stable while pointing at code which should not receive build authority. The scoped token protects unrelated namespaces while leaving its own namespace at risk.

For each attack path, retain the facts rather than averaging them:

  1. compromised identity and mutable object;
  2. precondition and observable change;
  3. preventive and detective controls;
  4. verification point and rejection behavior;
  5. recovery, revocation, or rotation action;
  6. reduced blast radius; and
  7. residual risk and reversal condition.

The control should be reconsidered when its authority, credential scope, build platform, registry, consumer population, or recovery objective changes. That is a more useful trigger than crossing an arbitrary aggregate score.

Hosted provenance is not always the cheapest answer

The complete path can be expensive: isolated builders, identity-bound provenance, immutable registry objects, policy engines, revocation, retained evidence, and consumer verification all require ownership.

A small manually built release can be a credible alternative when releases are rare, distribution is narrow, the consumer set is known, and the threat model does not justify hosted provenance infrastructure. Manual does not mean uncontrolled. It still needs a reviewed source revision, named trusted builder, artifact checksum, signing-key custody, publication boundary, and verification before use.

The fixture cannot choose that trade-off for a production system. It contains no real repository policy, hosted builder boundary, dependency ecosystem, release frequency, registry retention, consumer population, or recovery objective. Its keys, repository, registry, and artifact are synthetic.

What it does retain is inspectable: 10 consumer verification cases, 12 authority edges, seven attack paths, two publication permission boundaries, 24 rejected semantic counterfactuals, 11 primary-source responses, and a clean export of commit 4f2851b9042d9e386c7c8c070babde48efa85776.

Before adding another scanner or badge, draw the path of one artifact which a consumer actually executes. Name who can change it at every seam. Name what the next boundary verifies, what happens on failure, and which risk survives.

The unanswered edge, not the number of installed controls, is where the next supply-chain decision belongs.