# The Release Trust Read: Accept, Hold, or Block a Dependency Update

*You will score one newly published release across identity, provenance, and behavior and reach an accept, hold, or block verdict with a reason a teammate can re-check.*

- Canonical URL: https://www.refolk.ai/guides/release-trust-read-accept-hold-block
- Pillar: Engineering and open source
- Format: Framework
- Published: 2026-09-03
- Last reviewed: 2026-09-03
- Reading time: 17 min
- Keywords: is this npm update safe to install, detect npm account takeover release, verify package provenance before upgrade, review dependabot update supply chain, minimum release age policy

## Key takeaways

- npm audit signatures validates the registry's signature, not the publisher's, so every 2026 account-takeover release passed it cleanly - a green result never means the publisher is confirmed.
- The single highest-yield read is provenance that was present on prior versions and is absent now, because axios and mastra were both published manually to bypass OIDC CI.
- In Table B every incident had an exposure window under 24 hours, so pnpm's 1-day cooldown default blocks 4 of 4, at the cost of freshness rather than safety.
- The axios payload changed no source code; it only added plain-crypto-js@4.2.1, an unused runtime dependency whose postinstall dropped a RAT, so a manifest diff catches what a code review misses.
- pnpm's date-based trust-downgrade check false-fires on legitimate back-ported patches like semver@6.3.1, so treating every ERR_PNPM_TRUST_DOWNGRADE as an incident will burn a team out.
- In Refolk's index, US professionals titled Application or Product Security Engineer tagged with supply chain security returned only 2 matches and Germany returned 0, so this review usually falls to generalist appsec or platform engineers.

A new version of a dependency you already trust just landed in a Dependabot PR or an `npm update`, and you have to decide whether to pull it in. This guide is for the engineer or appsec generalist who owns that merge button. It gives you a way to score one specific release across identity, provenance, and behavior, and reach an accept, hold, or block verdict with a reason a teammate can re-check.

Most dependency guidance scores a whole project or a package that has already been flagged. This one scores a single new release of a library you have used for months, at the moment the upgrade PR arrives. That is a different job. The question is not "is this a good library" but "did this particular version come from the same pipeline as the last one, or from someone who took over the account."

## What the release trust read decides

The release trust read answers one question: does this new version come from the same trusted process that produced the version you already run, or has something in the publish path changed in a way that looks like a takeover. The output is one of three verdicts - accept, hold, or block - plus a one-line reason.

You run this read at a specific moment: the upgrade PR has landed, the diff is small, and the temptation is to merge on green CI. The whole point is to insert a short, repeatable read before that merge, aimed only at the signals that separated the 2026 account-takeover releases from routine updates.

Three families of signal drive the verdict:

- **Identity.** Who published, and did the publishing account or its email change.
- **Provenance.** Was the artifact built and signed by a known CI workflow, and did prior versions carry the same evidence.
- **Behavior.** Did the release add a runtime dependency nobody imports, or an install script that runs on `npm install`.

**2 - US appsec engineers tagged with supply chain security in Refolk's index**

Germany returned zero, so this review usually falls to generalist appsec or platform engineers, not a named specialist.

That thin count matters for staffing. In Refolk's index of professional profiles, US professionals titled Application or Product Security Engineer tagged with supply chain security returned only 2 matches, and Germany returned 0. Dedicated supply-chain-security titles are still rare. The read below is written so a generalist can run it without a specialist on call.

## Read provenance before you read anything else

Provenance is the strongest install-time signal, and its absence where it used to be present is the single highest-yield read. When a package is published via trusted publishing from GitHub Actions or GitLab CI/CD, npm automatically generates and publishes provenance attestations, giving a public SLSA Build Level 3 attestation that names the exact repository, commit, and workflow that produced the artifact.

You read it from the registry metadata. Run `npm view <pkg>@<new> --json` and inspect `dist.attestations`. If `dist.attestations.url` is present, provenance exists, and the in-toto SLSA predicate names the source repo, commit, and workflow. A manual token publish from a laptop typically has no `dist.attestations` and a `gitHead` that matches no signed CI build.

The reason this read beats everything else is behavioral. Attackers avoid the pipeline. Both the axios and mastra 2026 compromises were published manually to bypass OIDC CI, which is exactly why prior versions carried provenance and the malicious ones did not. "Provenance present before, absent now" is the tell.

> **Rule:** Compare against the prior version, not against zero
>
> A missing dist.attestations is normal for a project that has always published with a token. It is high-signal only when a prior version of the same package carried provenance. Always compare the new version's trust level against the previous one.

Two caveats keep this honest. Provenance is not generated for packages in private repositories and is not supported for CircleCI trusted publishing, so a legitimately private or CircleCI-published project will never have it. And when you do verify an attestation, use a current verifier: `slsa-verifier` 2.4.0 and earlier do not correctly verify npm's publish attestation signature, so an old verifier accepts a tampered attestation.

## Why a green signature check is not enough

`npm audit signatures` verifies the registry's signature, not the publisher's, so a hijacked-account publish passes it cleanly. This is the most dangerous false comfort in the whole review, because the command feels like it confirms authorship and does not.

Here is the mechanism. `npm audit signatures` validates registry signatures against npm's public keys and validates any provenance attestations against sigstore certificates. But npm operates the signing infrastructure on the publisher's behalf. A compromise of an individual package's publish credentials still produces a signature that verifies. Every 2026 account-takeover release would show green.

> The signature and the publisher are two different questions, and the green check only answers the first.

So the signature check earns its place only as a coarse integrity gate. It tells you the artifact was not corrupted in transit. It does not tell you the right person published it. Treat provenance and publisher identity as separate, load-bearing reads, and never let a green `audit signatures` stand in for "publisher confirmed."

## The identity and behavior signals that flagged the 2026 takeovers

The takeovers shared a compact signal set, and any one of them appearing on an update you did not expect is enough to hold. The composite is: a maintainer email or account change, a manual CLI publish, provenance absent where prior versions had it, a newly added and unused runtime dependency, and an added postinstall script.

Two incidents anchor the pattern. In the axios compromise on March 31, 2026, the attacker changed the primary maintainer's account email to an attacker-controlled ProtonMail address and used stolen credentials to publish `axios@1.14.1` and `axios@0.30.4` directly via the npm CLI, bypassing the OIDC-signed pipeline. The payload changed no source code. It added `plain-crypto-js@4.2.1`, a package that had not existed before that day and that axios never imports, whose only job was a postinstall script that dropped a cross-platform RAT.

In the mastra compromise in June 2026, the threat actor gained control of the `ehindero` account and published new versions of 140 or more packages across the `@mastra` scope, each injected with `easy-day-js` as a new dependency and all tagged `latest`, published manually, with provenance missing where every prior version had it.

#### The takeover shape, in order

1. **Account seized** - Maintainer email changed to an attacker address
2. **Pipeline bypassed** - Version published manually via npm CLI, not OIDC CI
3. **Provenance vanishes** - New version lacks the attestation prior versions carried
4. **Phantom dep added** - An unused runtime dependency is injected into package.json
5. **Payload runs** - A postinstall script executes on install

*Every 2026 account-takeover release in the dossier followed this sequence, which is why identity and behavior beat a code review of the package itself.*

The lesson from axios is that the code review misses the attack. The library's own source was untouched. The only change was a dependency in the manifest that nothing imports. That is why step four of the procedure diffs `package.json` and greps the codebase for actual imports, rather than reviewing the package's diff line by line.

> **Watch out:** gitHead alone proves nothing
>
> gitHead can be set from any machine and does not by itself prove CI origin. Confirm two things together: that gitHead resolves to a real tagged commit upstream, and that the provenance attestation names the same commit and workflow.

## Set the age gate, then read the release

Apply a minimum release age before a human looks, because most malicious releases are caught and removed within hours. A one-day cooldown is empirically sufficient for the opportunistic attacks in the record, and its cost is freshness, not safety.

The minimum release age, or cooldown, is a policy that refuses to resolve a version until it is at least N days old. The evidence for a short window is direct. The September 2025 debug and chalk attack was resolved in roughly 2.5 hours, Shai-Hulud 2.0 took about 12 hours, and both axios versions were removed within about 3 hours. A one-day cooldown would have blocked all of them. A stronger 3-to-7-day policy covers more of the record, since eight of ten examined supply chain attacks had windows under a week.

Tooling defaults vary, so know what your package manager does out of the box.

| Tool | Default window | Enforced at install? |
| --- | --- | --- |
| pnpm v11 | 1 day (1440 min) | Yes |
| Yarn Berry (npmMinimalAgeGate) | 3 days | Yes |
| Renovate 42 preset | 3 days | Merge-gated |
| Bun (minimumReleaseAge) | Opt-in, none default | Yes when set |
| npm CLI | None | No |

The npm CLI has no cooldown feature at all, so if you install with plain npm you must gate age elsewhere, typically in Renovate or a CI check. pnpm shipped `minimumReleaseAge` in version 10.16 in September 2025, covering direct and transitive dependencies, and defaults to one day in v11.

Against the record, the short window holds up.

| Incident (date) | Exposure window | Blocked by 1-day? | Blocked by 3-day? |
| --- | --- | --- | --- |
| debug/chalk (Sep 2025) | ~2.5 hours | Yes | Yes |
| Shai-Hulud 2.0 (Nov 2025) | ~12 hours | Yes | Yes |
| axios (Mar 31 2026) | ~3 hours | Yes | Yes |
| atool burst (May 19 2026) | ~22 minutes | Yes | Yes |

All four windows are under 24 hours, so a 1-day cooldown blocks 4 of 4 in this set. The atool row is a reminder of scale: on May 19, 2026 a compromised account published 637 malicious versions across 317 packages in about 22 minutes, and 61,274 npm write tokens were invalidated in response. A cooldown does not need to be long to catch a burst that fast.

I ran this search: `Security researchers who analyzed the axios or mastra npm compromises in 2026` - [see the full result list](https://www.refolk.ai/s/2kgy9wjcr5).

*Returns the people who published teardowns of these exact incidents, which is who you want reviewing your cooldown and provenance policy.*

When you need a second opinion on your policy or the read itself, [Refolk](/) finds the people who did the primary analysis of these compromises rather than the people who reposted the headline. Because dedicated supply-chain-security titles are thin, searching by what someone actually analyzed beats searching by job title.

## The trust-downgrade check and its false alarm

pnpm's `trustPolicy: no-downgrade` fails an install when a version resolves to a lower trust level than a previously published version, which catches the takeover pattern automatically. Trust levels are ordered trusted publisher, then provenance, then none, and the check shipped in pnpm v10.21 in November 2025.

The mechanism maps cleanly onto verdicts. If a prior version had provenance or a trusted publisher and the new one has neither, the install fails with `ERR_PNPM_TRUST_DOWNGRADE High-risk trust downgrade (possible package takeover)`. That is the same "provenance vanished" read, enforced at install time.

| Prior version trust | New version trust | pnpm no-downgrade result | Suggested verdict |
| --- | --- | --- | --- |
| trusted publisher | none | Fails (block) | BLOCK / investigate |
| provenance | none | Fails (block) | BLOCK / investigate |
| provenance | provenance | Passes | Continue checks |
| none (pre-provenance) | none | May false-fail via date rule | HOLD, allowlist if benign |

The critical caveat, and the framework's own false positive: trust checks are based solely on publish date, not semver. A patch release for a previous major version that never had trust evidence is treated as a downgrade because it was published after a later major version that did. This is the semver 6.3.1 case, and it also hits Storybook back-ported patches. Trust was added to the current main line but not back-ported to the old release still receiving patches.

> **Note:** Do not page on every downgrade error
>
> Because the check is date-based, a legitimate back-ported patch trips the takeover alarm. A team that treats every ERR_PNPM_TRUST_DOWNGRADE as an incident will burn out. The correct move is a same-major or age-based exclusion via trustPolicyExclude or trustPolicyIgnoreAfter.

## Run the read: from PR to verdict

This is the procedure, in order. On a routine update it takes about twenty minutes of a reviewer's time, most of which the age gate and CI run for you. Some teams put the age gate first as an automatic pre-filter, since pnpm and Renovate enforce it before a human looks; the metadata read below only matters for releases that clear or are exempted from cooldown.

#### The release trust read, step by step

1. **Capture the diff** - Pull the exact old and new version numbers and the changed package name from the Dependabot or PR lockfile change. Done when both versions and the name are recorded on the PR.
2. **Read provenance** - Run npm view <pkg>@<new> --json and inspect dist.attestations for the new and prior version. Done when you know whether provenance exists now and whether the prior version had it.
3. **Read publisher identity and gitHead** - Check the publishing account, whether the maintainer email or account changed, and whether gitHead resolves to a real tagged commit upstream. Done when the publish path is classified as OIDC-CI or manual-token.
4. **Diff the manifest** - Compare package.json between versions for new runtime dependencies and for added install, preinstall, or postinstall scripts. Done when you have a list of new deps and scripts, each marked imported or unused.
5. **Run signature and downgrade checks** - Run npm audit signatures, and on pnpm attempt an install with trustPolicy set to no-downgrade. Done when pass or fail is recorded, with any trust-downgrade error captured.
6. **Apply the age gate** - Confirm the release is older than your policy window, typically 1 to 7 days. Done when the release age is known and compared against the window.
7. **Score and record the verdict** - Map the signals to accept, hold, or block and write a one-line reason a teammate can re-check. Done when the verdict and reason are committed to the PR.

The verdict mapping is where the signals converge. There is no single published industry rubric, so the thresholds below are the defensible components a team can standardize on, drawn straight from the incident record.

**Accept / hold / block rubric**

```
BLOCK if any of:
  - provenance is absent where a prior version had it (trust downgrade)
  - a new runtime dependency is added and nothing imports it
  - a new install/preinstall/postinstall script appears
  - the maintainer email or publishing account changed for this release
HOLD (apply cooldown, do not merge) if any of:
  - the release is younger than the policy window (1-7 days)
  - provenance is absent and this project normally publishes via OIDC
  - pnpm no-downgrade fails but the version is an older-major back-port
ACCEPT only if all of:
  - OIDC provenance present, verified with slsa-verifier > 2.4.0
  - gitHead matches a tagged CI commit named in the attestation
  - no new runtime deps and no new lifecycle scripts
  - past the age window
Reason (one line): ____________________
```

*Paste into your PR template. Evaluate top to bottom and stop at the first match.*

The matrix below shows why identity and provenance interact rather than combine additively. A publish path change is only alarming when the artifact also lost its build evidence.

#### Publish-path change against provenance state

Horizontal axis runs from Provenance intact to Provenance vanished. Vertical axis runs from Same publish path to Publish path changed.

| Quadrant | What it means |
| --- | --- |
| Same path, provenance intact | Accept once age and manifest pass |
| Same path, provenance gone | Hold and investigate the pipeline |
| Path changed, provenance intact | Accept with a note; confirm the new workflow |
| Path changed, provenance gone | Block; this is the takeover shape |

*A manual publish is routine for token-only projects; it is a block only when provenance also disappeared.*

## How this read goes wrong

The read fails in predictable ways, and each failure has a specific counter-check. Knowing the false positives is what keeps the standard usable, because a rule that pages on every back-ported patch gets turned off.

- **`npm audit signatures` gives false comfort.** It passes on a hijacked-account publish because the registry signature verifies regardless of who published. Counter: read provenance and publisher identity separately, and never treat a green result as "publisher confirmed."
- **Absent provenance read as benign.** A missing `dist.attestations` is normal for token-published projects but high-signal when prior versions had it. False positive: a project that legitimately publishes via Yarn, as with Storybook 10.x. Counter: compare against the prior version's trust level, not against zero.
- **Trust-downgrade false alarm on back-ported patches.** `ERR_PNPM_TRUST_DOWNGRADE` fires on an older-major patch published after a newer provenance-carrying release, because the check is date-based. Counter: confirm the flagged version is an older major line, then allowlist via `trustPolicyExclude` or `trustPolicyIgnoreAfter`.
- **Phantom dependency missed by a version-only review.** A malicious release may change nothing in the package's own code and only add an unused runtime dep. Counter: diff `package.json` dependencies and grep the codebase for actual imports of any new dep.
- **gitHead assumed valid.** `gitHead` can be set from any machine and does not itself prove CI origin. Counter: confirm it resolves to a real tagged commit and that provenance names the same commit and workflow.
- **Cooldown bypass on "critical patch."** Teams add urgent fixes to `minimumReleaseAgeExclude`, which is exactly the exemption an attacker's "security fix" framing exploits. Counter: require independent disclosure verification before excluding anything.
- **Verifier too old.** Verifying with `slsa-verifier` 2.4.0 or earlier accepts a tampered publish attestation. Counter: pin a patched verifier version and record which one you ran.

> **Tip:** The manifest read outlasts the postinstall heuristic
>
> npm v12 disabled automatic lifecycle scripts by default on June 9, 2026, which removes the primary delivery vector. Attackers will move to payloads that do not need a postinstall hook, so keep the manifest-and-provenance read as your core signal rather than leaning only on "block postinstall."

## Before you merge

Run this last pass before you record the verdict. If any line is unchecked, the verdict is hold or block, not accept.

#### Pre-merge verification

- [ ] Old and new version numbers and the package name are recorded on the PR.
- [ ] dist.attestations was read for both the new and the prior version.
- [ ] The publish path is classified as OIDC-CI or manual-token, and any maintainer email change is noted.
- [ ] gitHead resolves to a tagged upstream commit that the provenance attestation also names.
- [ ] package.json was diffed for new runtime deps and new install scripts, each marked imported or unused.
- [ ] npm audit signatures was run and its result recorded as integrity-only, not publisher-confirmed.
- [ ] The release is past the policy age window, or an exclusion was justified with independent disclosure verification.
- [ ] A one-line verdict and reason are committed to the PR for a teammate to re-check.

## Keeping the read current

The ecosystem is shifting the default under the attacker, so revisit the mechanism rather than the specific thresholds. npm v12 disabling automatic postinstall on June 9, 2026 removes one delivery class, which pushes attackers toward payloads that do not need a lifecycle script. That is why the durable core of this read is provenance and manifest, not any single "block postinstall" rule.

Two things are worth re-checking on a schedule. First, your package manager's cooldown default: pnpm defaults to one day, Yarn Berry and Renovate to three, and the npm CLI to none, and these move. Second, your verifier version, since attestation-verification bugs like the `slsa-verifier` 2.4.0 issue mean an old tool silently accepts tampered evidence.

The staffing signal is worth watching too. Because dedicated supply-chain-security roles are still thin - in Refolk's index, 2 matches in the US and 0 in Germany for that specialization - this review will keep landing on generalist appsec and platform engineers. Write the rubric so any of them can run it, and keep the reason line mandatory so the verdict survives a handoff.

## Frequently asked questions

### Is this npm update safe to install if npm audit signatures passes?

No, a passing npm audit signatures does not mean safe. It validates the registry's signature, not the publisher's, because npm operates the signing infrastructure on the publisher's behalf. A compromise of an individual package's publish credentials still produces a signature that verifies cleanly, which is exactly why the axios and mastra takeover releases passed. Read provenance and publisher identity separately before you trust the update.

### How do I detect an npm account takeover release?

Look for the composite signal set that appeared in the 2026 takeovers: a maintainer email or account change, a manual CLI publish, provenance that is absent where prior versions had it, a newly added and unused runtime dependency, and an added postinstall script. No single one is proof, but provenance disappearing where it existed before is the highest-yield read and is what pnpm's date-based trust-downgrade check flags as a possible package takeover.

### What is a defensible minimum release age policy?

A one-day cooldown is empirically sufficient for opportunistic attacks: every incident in the dossier had an exposure window under 24 hours, so pnpm's 1440-minute default would have blocked all four. A stronger 3-to-7-day window covers more cases, since eight of ten examined attacks had windows under a week. pnpm defaults to 1 day, Yarn Berry and Renovate to 3 days, and the npm CLI has no cooldown at all.

### How do I verify package provenance before upgrading?

Run npm view <pkg>@<version> --json and inspect dist.attestations. If the url field is present, provenance exists, and the SLSA predicate names the source repository, commit, and workflow. Verify it with slsa-verifier verify-npm-package or cosign verify-npm-provenance, and pin a verifier newer than 2.4.0, since slsa-verifier 2.4.0 and earlier do not correctly verify npm's publish attestation signature.

### How do I tell a maintainer handoff from a hijack?

A routine pipeline handoff keeps provenance intact, keeps gitHead pointed at a tagged CI commit, adds no unused runtime dependency, and adds no new install script. A hijack typically drops provenance because the attacker publishes manually to bypass OIDC CI, changes the maintainer email, and injects a phantom dependency that runs a postinstall payload. Diff the manifest and confirm the publish path before you accept.

---

*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/release-trust-read-accept-hold-block*
