Auditing the npm Trust Chain After ChainDrop: Tokens, OIDC, and Postinstall

Auditing the npm Trust Chain After ChainDrop: Tokens, OIDC, and Postinstall

pr0h0••23 min read•coding
AI Usage (94%)
On this page

Introduction: The npm Provenance Headline That Shouldn't Make Sense

"An npm worm compromised hundreds of packages, and every one of them carried valid provenance." Read that twice. If provenance meant safe, the sentence couldn't be written. So either the report is wrong, or a lot of people have been reading dist.attestations as a green checkmark it was never designed to be.

I think it's the second, and that's the more interesting story. What follows is an audit of the npm trust chain a publish actually travels — token scope, the OIDC publish boundary, and postinstall execution — with commands to inspect each link yourself. Confirmed facts and untested guesses stay clearly separated throughout.

What the OrcaRouter report states — 400+ packages, claimed valid provenance, published 2026-10-30

What I actually have is thin. OrcaRouter published a report dated 2026-10-30 about an npm worm it calls ChainDrop, which it says hit more than 400 packages, each shipping with valid provenance metadata. That's the claim: worm, scale, attestations that verify.

The material I could reach doesn't include the package list, the initial access vector, affected version ranges, or a CVE. I went looking. The syndicated copy was a headline and a one-line summary, not the report body.

Why "valid provenance" and "malicious npm package" are not contradictory claims

A provenance attestation is a signed statement about how a tarball got produced: which CI system built it, from which repository, at which commit, out of which workflow file. It's a chain-of-custody document. It says nothing about whether the code is benign, and structurally cannot — there is no field for that judgment.

"The build came from acme/widgets/.github/workflows/release.yml@refs/heads/main at commit a91f2c4" and "the build scrapes NPM_TOKEN out of the environment and republishes itself" describe the same event perfectly well. Hand an attacker a maintainer session, let them publish through the project's own release workflow, and you get exactly that pair.

Nothing here is broken in Sigstore or in npm's implementation. What's broken is how the signal gets consumed. My position, up front: provenance is an identity check, and treating it as a safety check is the bug that made the ChainDrop headline feel like a surprise.

Scope of this audit and what I could not verify independently

What follows is a walkthrough of the trust chain an npm publish actually travels — token scope, the OIDC boundary, postinstall execution — with commands to inspect each link yourself. What I'm not doing is confirming ChainDrop specifics I can't see: the package list, the count, the initial access vector, or whether every one of those 400+ attestations really verified. Where the argument would need those facts, I write "unknown" instead of filling the gap.

Separating Confirmed Facts From Inference in the ChainDrop Case

This split matters more than usual, because the interesting part of the incident is a mechanism, and mechanisms are easy to assert past the evidence.

Confirmed — what the report explicitly asserts and what a provenance bundle structurally proves

ClaimStatusBasis
A worm named ChainDrop affected npm packagesReportedOrcaRouter report, dated 2026-10-30
The affected count is "more than 400 packages"ReportedSame report
The packages shipped with valid provenanceReportedSame report
Provenance bundles bind builder identity, repo, and commit — not code behaviorConfirmed by constructionnpm provenance docs, Sigstore spec, SLSA provenance spec
A valid attestation can wrap hostile codeConfirmed by constructionFollows from the row above; no registry-side malware analysis is part of the attestation

Those last two rows I can state without the report, because they're properties of the format rather than of this incident.

Inference — how a worm plausibly propagates across maintainer boundaries (marked as untested hypothesis)

My working model, and it is only a model: the attacker gets hold of a credential that can publish — an npm token, a session, or a CI workflow they control — then runs the same publish path again for every package that credential reaches. If the publish step runs in CI with id-token: write and --provenance, the registry signs the result faithfully, because that's its job.

I have not tested this against ChainDrop. It's the mechanism I'd expect given how the chain is wired, and it's falsifiable — see the section below on what would break it.

Unknown — CVE assignment, affected version ranges, takedown timeline; stating these as unknown rather than guessing

As of my source, unknown: any CVE identifier, the affected version ranges per package, whether the malicious versions are still installable, the takedown timeline, how many distinct maintainer identities were involved, and whether initial access came from a phished session, a leaked token, or something in the publish pipeline itself. If you need those to make a decision, you need the report and the registry — not this post.

⚠️

If you are triaging right now, don't wait on the full report. Run npm audit signatures and diff lifecycle scripts against your lockfile — both are cheap, and both give you an answer today.

What npm Provenance Actually Asserts

The publish flow: npm publish --provenance, GitHub Actions OIDC, Fulcio certificate issuance, Rekor transparency log entry

The sequence, as documented by npm and Sigstore:

  1. npm publish --provenance runs inside a supported cloud CI (GitHub Actions or GitLab CI/CD). Anywhere else it can't get an identity token, so the publish either fails or falls back to unsigned.
  2. The CLI requests an OIDC token from the CI provider. On GitHub Actions this needs permissions: id-token: write on the job.
  3. The OIDC token is exchanged with Fulcio for a short-lived signing certificate. That certificate embeds the workflow identity — on GitHub, something like https://github.com/org/repo/.github/workflows/release.yml@refs/heads/main.
  4. npm builds an in-toto statement describing the build, signs it, and uploads the signature to the Rekor transparency log, which hands back a log index and inclusion proof.
  5. The resulting Sigstore bundle is attached to the package version and served from the registry.

Steps 3 and 4 carry the weight. The certificate says who. Rekor says when, publicly and append-only.

What lands in the attestation — subject digest, builder identity, source repository, commit SHA, workflow ref, build environment

The subject is the tarball digest: SHA-512 over the exact bytes you downloaded. The predicate carries builder identity, source repository URI, commit SHA, workflow ref, build invocation ID, and a little environment metadata.

Predicate type varies. npm has emitted https://slsa.dev/provenance/v0.2, and SLSA v1.0 uses https://slsa.dev/provenance/v1. Check which one you got before writing a parser — field paths differ between them, and I handle both below.

What the attestation never asserts — no code review, no malware scan, no human approval, no judgment about intent

Read that list again. There's no field for "a human looked at this." None for "this passed a scanner." None for "the maintainer meant this." The attestation is a statement from the builder about the build, and CI is not a reviewer — CI is a machine that runs whatever the commit contains.

If the workflow at that ref, at that commit, runs node publish-and-steal.js, the attestation is still truthful.

The threat model gap: attestation binds who built it and from what commit, not what the code does

The gap is one word wide: behavior. Everything upstream of the tarball is covered. The tarball itself is covered only as bytes — its digest is signed, which proves the bytes you got are the bytes that were built, not that they're safe to execute.

Provenance answers "did this come out of the project's real release pipeline?" That's a genuinely useful question. It is not the same question as "should I run this?" Conflating them is the incident.

Reading an npm Provenance Bundle Yourself

You don't need a dashboard for any of this. Four commands cover most of it.

Inspecting attestations with npm view <pkg> dist.attestations and npm audit signatures

Start with the packument field. The output below shows the shape of the response with values elided — substitute a package you actually depend on.

npm view some-package dist.attestations --json
{
  "url": "https://registry.npmjs.org/-/npm/v1/attestations/[email protected]",
  "provenance": {
    "predicateType": "https://slsa.dev/provenance/v0.2"
  }
}

Note the asymmetry: dist.attestations present means the publisher opted into provenance for that version. Its absence on a package that has shipped attestations before is itself a signal — see the detection section.

Then verify the whole tree at once:

npm audit signatures
audited 214 packages in 3s
214 packages have verified registry signatures
198 packages have verified attestations

Two numbers, two different checks. Registry signatures mean the tarball matches what the registry served. Attestations mean the tarball matches a documented build. 198 of 214 is normal — 16 transitive packages have no provenance, often because they're old or published outside CI. 16 missing is fine. 6 missing on packages that had attestations last week is not.

Verifying the Sigstore bundle and locating the Rekor entry

Fetch the bundle and decode the DSSE payload:

curl -s https://registry.npmjs.org/-/npm/v1/attestations/[email protected] \
  | jq -r '.attestations[0].bundle.dsseEnvelope.payload' \
  | base64 -d | jq .

Out comes the in-toto statement. The parts worth staring at:

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "subject": [{ "name": "pkg:npm/[email protected]", "digest": { "sha512": "…" } }],
  "predicateType": "https://slsa.dev/provenance/v0.2",
  "predicate": { "builder": { "id": "…" }, "invocation": { "configSource": { "uri": "…" } } }
}

Confirm subject[0].digest.sha512 matches the tarball's integrity hash in your lockfile. If those diverge, stop — you're not looking at the artifact you installed.

Now find the transparency entry:

curl -s https://registry.npmjs.org/-/npm/v1/attestations/[email protected] \
  | jq '.attestations[0].bundle.verificationMaterial.tlogEntries[0].logIndex'

That integer is a permanent, public position in Rekor, and it's the field you cite in an incident report because nobody can rewrite it after the fact.

The field that matters most in an incident review: predicate.buildDefinition.externalParameters.workflow

During an incident the question is never "was this signed?" It's "which workflow signed it, and was that workflow supposed to be publishing?"

Predicate shape depends on version, so handle both:

curl -s https://registry.npmjs.org/-/npm/v1/attestations/[email protected] \
  | jq -r '.attestations[0].bundle.dsseEnvelope.payload' \
  | base64 -d \
  | jq -r '
      .predicate.buildDefinition.externalParameters.workflow
      // .predicate.invocation.configSource.uri
      // "NO WORKFLOW FIELD"'

Expected output:

some-org/some-package/.github/workflows/release.yml@refs/heads/main

For each affected version, record three things: workflow ref, commit SHA, Rekor log index. Then ask whether that workflow ref is the same one that published the last good version. If a package's entire history comes from release.yml@refs/heads/main and the malicious version comes from ci.yml@refs/heads/fix-lint, you've found the door.

Detecting a truthful-but-hostile attestation — a valid signature over a workflow the attacker controls

This is the case tooling won't flag, because there's nothing to flag. The attestation verifies. The identity is real. The workflow is real. The attacker created it.

Three variants to check for in any post-incident review:

  • New workflow file in the malicious commit. The attestation points at .github/workflows/publish-hotfix.yml, a file that didn't exist in the previous release. Signature valid, file is the attack.
  • Legitimate workflow, hostile commit. release.yml@refs/heads/main unchanged, but the commit it ran on is an attacker's branch merge. The workflow ref looks pristine; the SHA is the tell.
  • Environment bypass. A repo defines a release environment with required reviewers. The malicious workflow omits environment: release, so the protection rule never applies and the job publishes without approval. id-token: write alone is enough.

Only the third is a configuration bug you can fix. The first two are what "compromised publish identity" looks like from outside.

Classic automation tokens versus granular access tokens, and the blast radius of each when leaked

A classic automation token is account-scoped. Leak it and the attacker can publish every package you can publish, move dist-tags, and — depending on settings — touch account-level configuration. One phish, hundreds of packages. That number in the ChainDrop headline is what a leaked broad credential looks like.

Granular access tokens scope to specific packages and specific permissions (read vs. read-write), optionally with CIDR allowlists and a short expiry. Blast radius scales with the scope list, not the account.

I'm not subtle about this: classic tokens are a liability on any account maintaining more than one package, and 2FA-enforced granular tokens or OIDC trusted publishing should be the only publish paths left.

Auditing existing tokens with npm token list, checking 2FA mode, and pruning publish rights per package

npm token list
┌────────┬─────────┬────────────┬──────────────┬───────────────┐
│ id     │ token   │ created    │ readonly     │ CIDR whitelist│
├────────┼─────────┼────────────┼──────────────┼───────────────┤
│ 0a1b2c │ classic │ 2021-03-14 │ no           │ —             │
│ 7f8e9d │ granular│ 2025-11-02 │ no           │ 140.82.0.0/16 │
└────────┴─────────┴────────────┴──────────────┴───────────────┘

Two things to do with that table. Any row from before your current release process is suspect — delete it, then run a release to prove you didn't need it. And check the account's 2FA mode: npm profile get reports whether 2FA is enabled for auth-and-writes or auth-only. Auth-only 2FA does not protect publishing. That distinction gets missed by people who "turned on 2FA" three years ago and moved on.

Why a scoped token that can publish still defeats provenance as a safety signal

Here's the part that gets skipped. Say you migrate to granular tokens scoped to exactly the five packages you own. Good hygiene. But if that token sits in a CI job that also has id-token: write and runs npm publish --provenance, the token is a harvestable credential living in the environment of a process that executes postinstall scripts from your own dependency tree.

Provenance doesn't care how the publish was authorized. It attests the build. A stolen granular token used from an attacker's fork of your workflow — if that identity can be minted — or used for a token-only publish with no provenance at all (which is allowed) gives you either a truthful attestation of an attacker-controlled build, or nothing.

Scope limits how many packages fall. It does not make the signature meaningful as a safety claim.

How npm trusted publishing works — id-token: write, no NODE_AUTH_TOKEN, short-lived credentials exchanged at publish time

Trusted publishing removes the long-lived secret entirely. The job needs permissions: id-token: write, the package must be configured to trust that specific repo/workflow/environment combination, and the CLI exchanges the CI's OIDC token for a short-lived publish credential at the moment of publishing.

name: release
on:
  release:
    types: [published]
permissions:
  contents: read
  id-token: write
jobs:
  publish:
    runs-on: ubuntu-latest
    environment: release
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
      - run: npm ci
      - run: npm publish --provenance --access public

No NODE_AUTH_TOKEN. No NPM_TOKEN secret. Nothing to exfiltrate that outlives the job.

Version note, flagged as a caveat rather than something I'm certain of: trusted publishing needs a recent npm CLI, and the minimum moves. Check npm's docs for the current floor before you pin a version — and pin it, because npm install -g npm@latest in a release job is its own supply-chain decision.

The misconfigurations that quietly reopen the token path: wildcard environments, over-broad branch rules, leftover .npmrc secrets

The ones I keep finding in real repos:

  • environment: release in the docs, absent from the workflow. Protection rules are opt-in per job. If the job doesn't name the environment, reviewers are never asked.
  • Branch rules that match more than you think. on: push: branches: ['*'] matches every branch, including one an attacker with push access can create. Whether npm's trusted-publisher config binds to a branch or an environment at all depends on your setup — verify what yours actually accepts.
  • A leftover NODE_AUTH_TOKEN. If the secret is still in the job environment, it's still exfiltratable regardless of which auth path npm picks. I won't claim which path wins on a given CLI version; I haven't tested that matrix, and the safe answer is to delete the secret so the question stops existing.
  • pull_request_target with secrets in scope. A workflow that runs on pull_request_target, checks out the PR head, and has id-token: write is a publish pipeline with an attacker-controlled commit.

Reading a workflow file for the real publish boundary rather than the documented one

The documented boundary is "releases publish from release.yml." The real boundary is the set of (workflow, ref, environment) triples the registry will accept a publish from. Diff them.

Three greps map it quickly:

grep -rn "id-token: write" .github/workflows/
grep -rn "npm publish" .github/workflows/
grep -rn "NODE_AUTH_TOKEN\|NPM_TOKEN" .github/workflows/

Any file matching line 1 and line 2 is a publish boundary. Any file matching line 3 is a credential leaking into that job's environment. The gap between that set and the set you intended is your real attack surface.

Lifecycle script order (preinstall, install, postinstall) and exactly which process executes them

When npm installs a dependency it runs that package's lifecycle scripts in order: preinstall, install, postinstall. prepare also runs for git-sourced dependencies. These run for every package in the tree, not just the root.

The process is a shell — sh on POSIX, cmd.exe on Windows — spawned by npm, running as the user running npm, inheriting the full environment. In CI that means your GITHUB_TOKEN, cloud credentials from the OIDC step, your ~/.npmrc, any registry tokens, and the runner's network position inside your VPC.

That's the execution gap in one sentence: npm ci doesn't install code, it installs and runs it.

Why a signed tarball is still an executed tarball: integrity versus behavior

Integrity is "these bytes are the bytes that were published." Behavior is "these bytes do X when executed." Provenance strengthens the first. Lifecycle scripts are the second. No cryptographic link connects them.

A signed, provenance-verified tarball with this in package.json is fully attested and fully hostile:

{
  "scripts": {
    "postinstall": "node ./scripts/setup.js"
  }
}

setup.js runs before your test suite, before your linter, before any human sees a diff of what it did.

Runner-side controls — --ignore-scripts, --foreground-scripts, allowlisting, and the maintainer's dilemma when scripts are load-bearing

npm ci --ignore-scripts

This skips lifecycle scripts for the whole tree, including your own project's. It's the highest-value line you can add to a CI job and it costs nothing.

--foreground-scripts is the opposite posture: run them, but make them talk. Use it when you're deliberately rebuilding a known set of native packages, so a script that suddenly makes network calls shows up in the log.

The allowlist pattern:

npm ci --ignore-scripts
npm rebuild sharp esbuild --foreground-scripts

The dilemma is real. Some packages genuinely need install-time work: sharp compiles or downloads libvips binaries, puppeteer fetches Chromium, native addons compile against the local toolchain. Telling those maintainers "just drop your postinstall" isn't advice, it's a wish.

The honest answer is that --ignore-scripts plus an explicit, reviewed npm rebuild allowlist turns an unbounded trust problem into a small auditable one. That's strictly better than implicitly trusting 214 packages, even though it isn't free. Worth noting: pnpm 10 made blocking lifecycle scripts the default unless a package appears in onlyBuiltDependencies. The ecosystem is converging on deny-by-default here, and npm's ignore-scripts=true in .npmrc is the equivalent posture.

Reconstructing a Worm's Spread Path Through npm Credentials

A plausible propagation model: compromise one publish identity, harvest credentials from the runner, publish dependents

Working model, explicitly untested against ChainDrop:

  1. Attacker gains one publish-capable identity — a phished session, a leaked automation token, or commit access to a repo whose release workflow will publish anything.
  2. The malicious version ships with a postinstall that reads process.env and ~/.npmrc and exfiltrates anything token-shaped.
  3. The victim's next CI run executes it. The attacker now holds a second credential, possibly for a completely different scope.
  4. Repeat step 2 with the new credential.

The count grows because credentials cover many packages, not because require() chains do. A worm that had to wait for dependency adoption would take months to reach 400 packages. A worm holding an account-scoped credential gets there in one publish loop.

Why provenance survives the jump intact — the worm publishes through the same trusted builder path

Each hop that publishes through the project's own CI produces an attestation that verifies cleanly, and the registry has no reason to reject it. The signature covers a real workflow at a real commit — the attacker's commit in the maintainer's repo, or the attacker's workflow file in a repo they now control.

So "valid provenance on 400+ packages" isn't a paradox. It's the expected shape of credential-based spread through honest build infrastructure. Every individual attestation is true. The aggregate claim people reached for — "400+ signed packages must be safe" — was never implied by any single link.

What would falsify this model, and which logs an affected maintainer should pull first

Falsifiers I'd accept:

  • Affected packages have disjoint maintainer sets, no shared identity, no shared CI. Credential reuse across them becomes implausible, and you'd look instead at a compromised registry-side process or a shared build tool.
  • Attestations in the affected set come from many unrelated builder identities with no common origin. Same conclusion.
  • Registry logs show publishes from outside the projects' normal CI — token-only publishes with no attestation. Different mechanism, and it would contradict the report's "valid provenance" claim.

Logs to pull first, in order:

  1. The registry packument — npm view <pkg> time --json plus _npmUser per version. Who published, and when.
  2. The attestation set — workflow ref, commit SHA, Rekor log index per version, diffed against the last known-good release.
  3. CI run history around the malicious publish — triggered by release, by push, or by workflow_dispatch? By whom?
  4. Runner environment exposure — which secrets were in scope for that job. Rotate everything that was.
  5. npm token list on every account in the affected maintainer set. Anything created near the incident window is compromised.

A Practical CI-Level Defense Against npm Supply-Chain Worms

Publish-side checklist — granular tokens, 2FA enforced, OIDC-only publishing, locked environment rules, workflow diff review

  • Delete classic tokens. Granular only, scoped to specific packages, short expiry, CIDR allowlists where your CI IPs are stable.
  • 2FA auth-and-writes on every account with publish rights. Auth-only isn't enough.
  • Trusted publishing with id-token: write and no NODE_AUTH_TOKEN, NPM_TOKEN, or .npmrc auth line anywhere in the job.
  • environment: release on the publish job with required reviewers — and confirm the npm-side trusted publisher config binds to that environment, not just the repo.
  • Branch conditions that name one branch. Not '*'.
  • Workflow diff review in the release PR: if a release touches anything under .github/workflows/, that gets a separate review with a separate reviewer. This is the control that catches the truthful-attestation-over-hostile-workflow case.

Consume-side checklist — npm ci --ignore-scripts in CI, lockfile integrity review, dependency diff gates on postinstall additions

- run: npm ci --ignore-scripts
- run: npm rebuild sharp --foreground-scripts
- run: npm audit signatures

Three lines. The third turns a silent regression — a package that used to have provenance and now doesn't — into a CI failure.

For the diff gate, the check that pays for itself is a lifecycle-script diff between the version you have and the version you're upgrading to. Small enough to run on every PR:

script-diff.mjs
// Compares lifecycle scripts between two published versions.
// Usage: node script-diff.mjs some-package 1.4.1 1.4.2


const LIFECYCLE = new Set([
"preinstall", "install", "postinstall",
"prepare", "prepublishOnly",
]);

const [, , name, from, to] = process.argv;

function scriptsOf(version) {
// npm view can read arbitrary package.json fields as JSON.
const out = execFileSync(
  "npm",
  ["view", `${name}@${version}`, "scripts", "--json"],
  { encoding: "utf8" }
);
const parsed = JSON.parse(out || "{}");
const relevant = {};
for (const [key, value] of Object.entries(parsed)) {
  if (LIFECYCLE.has(key)) relevant[key] = value;
}
return relevant;
}

const before = scriptsOf(from);
const after = scriptsOf(to);
const added = Object.keys(after).filter((k) => !(k in before));

if (added.length > 0) {
console.error("NEW lifecycle script(s):", added.join(", "));
for (const key of added) console.error(`  ${key}: ${after[key]}`);
process.exit(1);
}

console.log("no new lifecycle scripts");

Run against a routine patch bump:

node script-diff.mjs some-package 1.4.1 1.4.2
no new lifecycle scripts

Run against the version that should worry you:

node script-diff.mjs some-package 1.4.2 1.4.3
NEW lifecycle script(s): postinstall
  postinstall: node ./scripts/setup.js

Exit code 1 fails the PR. That's the whole gate. It won't catch a hostile edit to an existing postinstall, so pair it with lockfile diff review — but it catches the additive case, which is the common one.

Detection signals worth alerting on — publish from a new workflow ref, publish outside the release window, new lifecycle script in a patch release

SignalWhy it mattersWhere to get it
Publish from a workflow ref never seen before for this packageHighest-signal indicator of a hostile-but-valid attestationAttestation workflow field, per version
Publish outside the release windowRelease jobs run on tag events, not at 03:00Packument time, CI run history
New lifecycle script in a patch releaseLegitimate patches rarely add install hooksscripts diff between versions
Provenance disappears on a package that had itToken-path publish, or a build moved off CInpm audit signatures, dist.attestations
Granular token created outside your release processNew publish credentialnpm token list per maintainer

The fourth row is the one I'd alert on hardest. Absence of provenance on a package with a multi-year attestation history beats any individual attestation's contents as a signal, because it means the publish path changed.

What Provenance Should Change in Review Practice

Treat attestations as an identity check, not a safety check

The question provenance answers: "is this the artifact from the pipeline we agreed on?" That's a real question, it's cheap to answer, and before provenance it was basically unanswerable.

The question it doesn't answer: "should this code run on my machine?" That still requires reading the diff, checking the maintainer, and controlling execution — --ignore-scripts, allowlisted rebuilds, network egress rules on CI runners.

If your dependency review has a step that says "has provenance → pass," replace it with "has provenance from the expected workflow ref at a reviewed commit → pass the identity gate," and keep a separate execution gate. The ChainDrop report is what the merged gate looks like in production.

Where provenance genuinely helps: narrowing the compromised-identity window during incident response

This isn't a consolation prize. During response, provenance collapses an unbounded question into a bounded one.

Without attestations, "which of my 400 packages are affected?" starts with "when did anything suspicious get published?" and ends in guesswork. With them, you sort every version by workflow ref, commit SHA, and Rekor log index, and the set published outside the normal triple is small, enumerable, and timestamped in a public append-only log.

Concretely: for each affected package, find the last version whose (workflow, ref, commit) triple matches the known-good pattern, mark everything after it as suspect, and cite the Rekor log index as the immutable reference in your writeup. That's a morning's work instead of a week's.

Provenance doesn't tell you the code was safe. It tells you, precisely and verifiably, where it came from. During an incident, that's the difference between a timeline and a shrug.

Further Reading

Primary sources — the OrcaRouter report, npm provenance docs, GitHub OIDC documentation, Sigstore specification

Share this post

More posts

Loading comments…