# The Build Provenance Bar for Package Versions

*You can grade a package version's build provenance and reach the same accept-or-hold verdict another engineer on your team would reach.*

- Canonical URL: https://www.refolk.ai/guides/build-provenance-bar-package-versions
- Pillar: Engineering and open source
- Format: Standard
- Published: 2026-09-10
- Last reviewed: 2026-09-10
- Reading time: 16 min

A dependency update lands, and you have to decide whether its build provenance is strong enough to let the version into your build, or whether to pin the current version and hold. This guide is for the engineer or reviewer who owns that call: it turns SLSA build levels, trusted-publishing status, and Sigstore attestation checks into a gradeable bar two people would score the same way, calibrated against the 2026 attacks where valid attestations were present but bypassed. Adopt it as merge policy.

The name for the bar is deliberate. A version does not "have provenance" or not. It clears a bar made of four independent checks, and if any one fails, the version holds.

## What build provenance proves, and what it never proves

Build provenance is a signed attestation that a build platform produced a specific artifact from a specific source. It answers "which pipeline built this," and nothing more. That single sentence is the whole reason this bar needs four layers instead of one.

Provenance records the build identity, the source, and the artifact: the builder identity, source repository, commit hash, build parameters, and the output artifact digest. When npm accepts a publish, it validates that the issuer matches the signing certificate's source repository URI extension, and the attestation is written to Sigstore's Rekor transparency log. That is real, checkable evidence. But it has a hard boundary.

The boundary is that provenance confirms which pipeline produced the artifact, not whether the pipeline was behaving as intended. A compromised build step produces a validly-attested but malicious package. The build track also does not validate source integrity: if you generate flawless provenance over a poisoned commit, you have accomplished nothing. In the PHP incident, attackers injected a backdoor commit, and the build track alone could not tell whether that commit was legitimate.

> Provenance answers which pipeline built this. It never answers whether the pipeline was honest.

So the bar treats provenance as one input. It is the check that tells you the artifact came from the repository and workflow you expected. It is not the check that tells you the artifact is safe.

## The four checks that make up the bar

A version clears the bar only when it passes all four checks below. Each proves one thing, and each has a failure signature: what it looks like when it lies to you.

| Check | What it proves | What it looks like when it lies |
|---|---|---|
| Signature integrity | The artifact is signed and attested by the registry | Green signature from a compromised publish credential, verifying cleanly |
| Source binding | Artifact came from the expected repo, commit, workflow, issuer | Valid attestation over a hijacked-but-legitimate pipeline |
| Trust regression | Trust evidence did not drop versus prior versions | A version stripped of provenance still installs if you only verify present attestations |
| Release-age floor | The version has survived long enough to be flagged | An undatable version slips past when metadata omits the publish time |

The order matters less than the completeness. Signature integrity and source binding are cryptographic: they answer identity questions. Trust regression and the release-age floor are behavioral: they answer "does this version fit the pattern of a healthy release." The 2026 attacks that beat provenance beat it on the cryptographic checks precisely because those checks were passing, which is why the behavioral checks carry so much weight.

> **Rule:** All four, every version
>
> A version clears the bar only if signature integrity, source binding, trust regression, and the release-age floor all pass. If any one fails, pin the current version and hold. There is no partial credit and no override for popular packages.

## Reading the SLSA build track without over-reading it

SLSA v1.2 defines a build track with four levels, L0 to L3, and each level tightens what the provenance guarantees. Grading a version means knowing what its level buys you and, more importantly, what it does not.

| Level | What it guarantees | What it does not cover |
|---|---|---|
| L1 | Provenance exists; may be incomplete or unsigned | Anything adversarial; it exists to prevent mistakes, not attacks |
| L2 | Hosted platform generates and signs provenance; consumer checks authenticity | Tampering during the build itself |
| L3 | Build runs are isolated; signing secrets are out of reach of build steps | Compromise of the build platform, such as a malicious administrator |

At L1, provenance may be incomplete or unsigned; the level exists to prevent honest mistakes. At L2, the producer runs builds on a hosted platform that generates and signs the provenance, the consumer checks it is authentic, and signed provenance lets you detect changes made after the build, assuming the signing keys and trust anchors are secure. At L3, the platform prevents one build run from affecting another and keeps signing secrets away from user-defined build steps, protecting against changes during the build from insiders, stolen credentials, or other tenants.

The trap is reading a green L3 badge as "safe." L3 reduces risk but does not cover compromise of the build platform itself. And as the next section shows, a hijacked token operating inside a legitimate L3 pipeline produces attestations that pass every cryptographic check. Grade the level for what it is: strong evidence of build integrity, not evidence of intent.

## How valid provenance still shipped malware

Two 2026 attacks defeated provenance in different ways, and the bar exists to catch both. Neither was a cryptographic break. One removed provenance; the other produced genuine provenance from a compromised pipeline.

The first was Axios. On March 31, 2026, an attacker who had hijacked a maintainer account published axios@1.14.1 and axios@0.30.4 with a trojanized dependency. The legitimate axios@1.14.0 was published via GitHub Actions OIDC trusted publishing and showed OIDC provenance, a trusted-publisher record, and a gitHead commit. The malicious versions had none of that. The bypass worked because the project still passed an NPM_TOKEN as an environment variable alongside the OIDC credentials, and when both are present npm defaults to the token. Any tool checking for provenance would have flagged the missing attestation instantly. The chain was live for roughly three hours, from 00:21 to 03:25 UTC, before npm removed the packages.

**174,000 - npm packages that depend on Axios**

Axios has over 100 million weekly downloads, so a three-hour compromise window reached far.

The second was harder. On May 11, 2026, the Mini Shai-Hulud worm compromised 84 npm artifacts across 42 @tanstack/* packages by publishing malicious versions through the project's own GitHub Actions release pipeline using hijacked OIDC tokens. The compromised packages carry valid SLSA Build Level 3 provenance attestations. This was the first documented npm worm producing validly-attested malicious packages. The same campaign compromised 373 malicious package-version entries across 169 npm packages. Provenance verification could not help here, because the attestation was genuine. Only a release-age cooldown, behavioral install-time analysis, and known-malicious signature checks could catch it.

> **Watch out:** A green L3 badge is not a clearance
>
> The Mini Shai-Hulud packages carried valid SLSA Build Level 3 attestations and were still malware. SLSA provenance confirms which pipeline produced the artifact, not whether the pipeline was behaving as intended. Never treat a passing attestation as the end of the review.

The lesson each attack teaches is a check on the bar. Axios teaches trust regression: the malicious versions dropped from trusted publisher to nothing, and a no-downgrade policy fires on that delta without any forgery to detect. Mini Shai-Hulud teaches the release-age floor: when the crypto is genuine, time is the only defense that still works, because compromised releases are usually removed within hours.

## The verification procedure, step by step

Grade a version by walking these steps in order. The first four are things you run on your machine or in CI; the last three are policy the resolver enforces. Sources disagree on whether the age cooldown runs first as a blanket gate or alongside trust at resolution, so treat the ordering below as one defensible sequence and keep both checks non-optional.

#### Clearing a version through the bar

1. **Record the expected identity** - Write down the exact name@version, the expected GitHub repository, and the expected release workflow path. Done means you have a written expected identity to compare every attestation field against.
2. **Pull the attestation from the registry** - Download the tarball and attestations using the .dist.attestations.url and .dist.tarball fields from npm view. If the attestations URL is absent, the version has no provenance and routes straight to the hold decision.
3. **Verify signature integrity** - Run npm audit signatures, which checks registry signatures and provenance attestations and errors on missing or invalid ones. Done means zero verification errors on npm CLI 9.5.0 or later.
4. **Bind the attestation to the intended source** - Run slsa-verifier or cosign with the source URI, certificate identity regexp, and OIDC issuer, then inspect the commit SHA via print-provenance. Repo URI, workflow path, OIDC issuer, and commit must all match step one.
5. **Check for trust regression** - Apply a no-downgrade policy so the version cannot install if any earlier-published version had stronger trust evidence, judged by publish date rather than semver.
6. **Apply the release-age floor** - Hold the version until it is older than your minimum release age, which defaults to 1440 minutes in pnpm.
7. **Reach the verdict** - Accept only if signature, source binding, trust regression, and release-age checks all pass; otherwise pin and hold, and record the failing check by name.

The commands behind these steps are concrete. To pull attestations, `npm view <pkg> --json | jq -r '.dist.attestations.url'` gives you the attestations URL and `.dist.tarball` gives the tarball. To bind to source, slsa-verifier takes `verify-npm-package` with `--attestations-path`, `--builder-id`, `--package-name`, `--package-version`, and `--source-uri github.com/...`. The cosign path uses `cosign verify-blob-attestation` with `--certificate-oidc-issuer="https://token.actions.githubusercontent.com"` and a `--certificate-identity-regexp` anchored to the owner, repo, and workflow file. Use `--print-provenance` and read the commit SHA of the config source or materials to confirm it matches your expected identity.

A version counts as verified only when the source repository URI, commit SHA, build workflow path, and OIDC issuer all match the intended source. Anything less is not a pass.

I ran this search: `Engineers who added SLSA provenance or npm trusted publishing to a CI pipeline, at a US software company.` - [see the full result list](https://www.refolk.ai/s/e8txn6rr1f).

*Returns people who have actually shipped this control, useful when you need someone to own the merge policy or fix a broken publish path.*

## The release-age floor as the load-bearing control

The release-age floor holds a version out of your build until it is old enough to have been flagged if it were malicious. It is the single control that would have caught both 2026 attacks, and it costs you nothing but a short delay on the freshest releases.

The mechanism is simple. Compromised releases are usually flagged and removed within hours, so even a 24-hour delay filters most incidents at the install layer. The Axios chain lived for three hours; a one-day floor would have kept every malicious version out of every build that respected it. Mini Shai-Hulud's genuine attestations were useless against a cooldown, because the packages were removed before the floor elapsed.

| Tool | Setting | Default or unit |
|---|---|---|
| pnpm | minimumReleaseAge | 1440 minutes (1 day default) |
| bun | minimumReleaseAge | seconds; introduced in Bun 1.3.0 |
| npm / yarn | npm audit signatures only | no built-in age gate |

pnpm's `minimumReleaseAge` defaults to 1440 minutes, so newly published packages are not resolved until at least a day old. You opt out with `minimumReleaseAge: 0`, which you should not do. pnpm also supports a `trustPolicy` setting; set to `no-downgrade` it blocks installation when a package's trust level decreased versus previous releases, judged solely by publish date, not semver. The three tiers, strongest to weakest, are trusted-publisher, then provenance-only, then no trust evidence. pnpm v11 added a lockfile verification gate that re-applies policy to every lockfile entry before fetch, closing a gap where policy was checked at resolution but not at install.

**24 hours - release-age delay that filters most supply-chain incidents**

Because compromised releases are usually removed within hours, a one-day floor blocks the common case at the install layer.

If your stack is npm or yarn, you have no built-in age gate, so the floor has to come from your CI policy or a wrapper. Treat that as a gap to close, not a reason to skip the control.

## How this goes wrong: failure modes and false positives

Most bad accept decisions come from trusting one check as if it were all four. Here are the ways a version clears a naive review while still being unsafe, with the check that catches each.

- **Green signature, wrong publisher.** The signature is the registry's, not the publisher's. A compromise of an individual package's publish credentials still produces a signature that verifies cleanly. Require provenance and a trusted-publisher record, not just `npm audit signatures`.
- **Valid provenance, malicious payload.** A compromised build step produces a validly-attested but malicious package, as Mini Shai-Hulud proved with 84 L3 artifacts. Reading a green SLSA L3 badge as "safe" is the false positive. Add cooldown plus behavioral scanning.
- **Token fallback bypass.** Trusted publishing only works when it is the sole publishing path. Axios did the right things yet fell because a long-lived NPM_TOKEN remained in the workflow and npm defaulted to it. Confirm no long-lived publish token survives in the release workflow.
- **Provenance simply absent.** The malicious Axios versions had no attestation at all while the legitimate one did. A policy that only verifies attestations that are present never fires on this. Treat "no attestation" as a hold via `no-downgrade`.
- **Attestation signed but source repo gone.** If the linked source commit or repository cannot be found, provenance can no longer be established, which happens when a repo is deleted or made private. Fail closed on an unresolvable source.
- **Cooldown silently bypassed.** pnpm skips the age check for packages whose registry metadata omits the time field. Set the strict behavior so undatable versions fail rather than install.
- **Two-document lockfile blind spot.** A scanner that reads only the first lockfile document reports no dependencies and no vulnerabilities without failing. Confirm your SCA tool parses both documents.

> **Tip:** Fail closed, always
>
> Every failure mode above has the same fix shape: when evidence is missing, ambiguous, or unresolvable, hold rather than install. The absence of a red flag is not a green light.

## The PyPI case is weaker at install time

If the version you are grading is a Python package, the bar still applies, but the install-time evidence is thinner, so you lean harder on cooldown and identity pinning. PyPI's model is real but not yet a gate.

PyPI uses PEP 740 attestations: in-toto v1 Statement objects signed through Sigstore, with the signature using ECDSA over the NIST P-256 curve with SHA-256. A publish attestation lets a consumer verify that a release distribution, sdist or wheel, was uploaded via a Trusted Publisher and not another mechanism such as a locally-held API token, and that a specific Trusted Publisher identity, for example a particular GitHub Actions workflow, was used. PyPI only permits attestations with a verifiable signature to be uploaded and redistributed.

Two facts change how you grade Python versions. First, pip and uv do not yet reject unsigned packages by default, so attestations today are evidence you record and audit rather than an install-time gate. Second, adoption is thin: as of November 2024, only about 5% of the top 360 most downloaded PyPI packages carried attestations, even though roughly 20,000 PyPI packages could attest by default with no changes. And as on npm, the signature covers who published the file, not what the file contains. For a Python accept decision, the cooldown and identity pinning do the load the missing install-time gate cannot.

## Who owns this policy, and the pre-merge checklist

This bar is adopted by generalists, not by a titled specialist, so it has to be a checklist anyone on the team can run. In Refolk's index the exact-title specialism is near-nonexistent: 1 US profile and 0 Germany profiles carry the title "Supply Chain Security Engineer" [OURS]. The capability lives inside DevSecOps and platform roles.

| Skill | US profiles | Ratio vs Sigstore |
|---|---|---|
| SLSA | 13 | 2.6x |
| Sigstore | 5 | 1.0x |

In Refolk's index, 13 US profiles list the skill "SLSA" versus 5 listing "Sigstore" [OURS], and the in-sample employers are firms like Datadog, Bloomberg, Amazon, Cisco, and Visa rather than dedicated supply-chain shops. If you need to hire or borrow this expertise, [Refolk](/) finds it by the work people have actually shipped rather than by a job title almost nobody holds. That diffusion is exactly why a gradeable checklist beats a specialist: it lets any reviewer reach the same verdict.

#### Versions narrowing through the bar

| Stage | Figure | Note |
| --- | --- | --- |
| Incoming versions | all | every update that lands |
| Passes signature integrity | most | npm audit signatures returns clean |
| Binds to intended source | fewer | repo, commit, workflow, issuer all match |
| No trust regression | fewer | no earlier version had stronger evidence |
| Clears release-age floor | accepted | older than the one-day cooldown |

*Each check removes versions that pass the previous one, so the accepted set is small and defensible.*

Run this before you merge any dependency update. Every item is checkable, so two engineers grading the same version reach the same verdict.

#### Pre-merge provenance checklist

- [ ] The exact name@version, expected repo, and expected workflow path are written down before verification starts.
- [ ] npm audit signatures runs clean on npm CLI 9.5.0 or later, with zero errors.
- [ ] The attestation binds to the expected source repository URI, commit SHA, workflow path, and OIDC issuer.
- [ ] An unresolvable source repository or commit fails the check closed, not open.
- [ ] No earlier-published version had stronger trust evidence than this one.
- [ ] The version is older than the release-age floor, or it is held until it is.
- [ ] Undatable versions with missing publish-time metadata fail rather than install.
- [ ] The release workflow has no long-lived publish token that could override OIDC.
- [ ] For PyPI versions, identity pinning and cooldown carry the decision since install-time gating is absent.
- [ ] The final accept-or-hold verdict is recorded with the failing check named if it is a hold.

## Keeping the bar current

The bar is evergreen in shape but not in its numbers, so re-check the mechanisms on a schedule rather than trusting a value you set once. Three things drift.

First, package-manager defaults change. Confirm your pnpm `minimumReleaseAge`, `trustPolicy`, and lockfile verification settings are still applied after every major upgrade, and check whether npm, yarn, or Bun have added or changed an age gate. Second, the attack surface moves. The August 2026 Shai-Hulud campaign compromised over 1,300 package versions representing a combined 2 billion monthly downloads, which is an order of magnitude larger than the May wave, so assume the next attack will again produce valid attestations and keep the cooldown non-negotiable. Third, PyPI's install-time story is improving; re-check whether pip or uv have begun gating on attestations by default, because when they do, you can shift some weight off cooldown and back onto identity binding for Python.

The one thing that does not drift is the principle. Provenance tells you which pipeline built the artifact. It never tells you the pipeline was honest. Keep all four checks, fail closed, and let time do the work cryptography cannot.

## Frequently asked questions

### What is the npm attestation verify command?

Run npm audit signatures on npm CLI 9.5.0 or later. It checks the registry signatures and provenance attestations for your installed tree and returns an error if any package has missing or invalid signatures or attestations. It confirms the artifacts are signed and attested, but it does not by itself confirm the publisher identity, so pair it with a source-binding check using slsa-verifier or cosign against your expected repository and workflow.

### Does valid SLSA provenance mean a package version is safe to update?

No. Provenance confirms which pipeline produced the artifact, not whether that pipeline behaved honestly. In May 2026 the Mini Shai-Hulud worm published the first npm attack carrying valid SLSA Build Level 3 attestations across 42 @tanstack packages, using hijacked OIDC tokens inside the project's own release pipeline. Valid attestation is necessary but not sufficient, so add a release-age cooldown and behavioral install-time analysis on top of it.

### How do I check a package's trusted-publishing status and whether it regressed?

Inspect the version's provenance for an OIDC trusted-publisher record, then compare it against earlier versions by publish date, not semver. A no-downgrade policy blocks installation if any earlier-published version had stronger trust evidence, for example a version that once had a trusted-publisher record but now has only provenance or none. This is what would have caught the malicious Axios releases, which dropped from OIDC provenance to no attestation at all.

### Why did the Axios compromise ship malware despite trusted publishing?

The Axios project passed a long-lived NPM_TOKEN as an environment variable alongside its OIDC credentials, and when both are present npm defaults to the token. An attacker who hijacked the maintainer account published malicious versions directly, bypassing CI/CD entirely. Trusted publishing only protects you when it is the sole publish path, so your checklist must confirm no long-lived publish token remains in the release workflow.

### How is provenance verification different on PyPI?

PyPI uses PEP 740 attestations, which are in-toto Statement objects signed through Sigstore using ECDSA over NIST P-256 with SHA-256. A publish attestation proves a distribution was uploaded via a Trusted Publisher rather than a locally-held API token, but the signature covers who published the file, not what it contains. Critically, pip and uv do not reject unsigned packages by default, so a Python team's accept decision must lean harder on release-age cooldown and identity pinning.

### What release-age cooldown should I set?

A one-day floor is a strong default. pnpm's minimumReleaseAge defaults to 1440 minutes, and Bun added a comparable setting in seconds from version 1.3.0. Because compromised releases are usually flagged and removed within hours, even a 24-hour delay filters most incidents at the install layer, including the Axios chain that was live for only about three hours. Make sure undatable versions fail rather than install, since some tools skip the age check when registry metadata omits the publish time.

---

*From the Refolk guide library. I revise these guides rather than replacing them, so the current version is always at https://www.refolk.ai/guides/build-provenance-bar-package-versions*
