A package quietly changes its name tag after the bouncer checked it
pnpm gates lifecycle script execution (postinstall, preinstall, install) behind an allowBuilds allowlist that defenders curate to limit which dependencies can run arbitrary code at install time. In versions prior to 10.34.2 and 11.5.3, the identity used to evaluate that allowlist was derived from manifest fields the package itself controls rather than the resolved registry identity. A crafted package — directly or via a transitive dependency — can present a manifest whose name matches an allowlisted entry, satisfy the gate, and execute attacker-controlled lifecycle scripts during pnpm install. CWE-346 (Origin Validation Error) captures the class precisely.
Vendor's HIGH (7.5) with AC:H and UI:R is defensible but slightly understates the real story. The 'high attack complexity' assumes the attacker still has to land a malicious package in the dependency graph — which the 2025–2026 npm supply-chain wave (Shai-Hulud, TanStack, lifecycle-token exfil) has shown is *not* a high bar. Once the package is in the graph, the allowBuilds bypass turns a 'we hardened install scripts' control into a paper one. We keep it at HIGH rather than upgrading to CRITICAL because the trigger still requires a developer or CI to run pnpm install against a contaminated lockfile, not a network-reachable service.
4 steps from start to impact.
Plant malicious package in dependency graph
npm-package-clone-script and the Shai-Hulud worm payload that mass-publishes hijacked versions.- Attacker can publish to a registry the victim's lockfile resolves against
- Victim consumes the package directly or transitively
- Internal registries with proxy-only mode and strict scope policies block public-package shadowing
- Lockfile pinning + integrity hashes detect substitution but NOT first-introduction
Craft a manifest that impersonates an allowBuilds-approved name
package.json whose name field matches an entry the victim has whitelisted in pnpm.onlyBuiltDependencies / allowBuilds. Because pnpm pre-10.34.2 / pre-11.5.3 evaluates the gate against manifest-supplied identity rather than the registry-resolved tuple, the spoofed name is accepted as trusted.- Victim project uses
pnpm.onlyBuiltDependenciesor equivalent allowBuilds policy (this control is *itself* the target — projects without it are arguably less impacted by this specific bug, more by lifecycle scripts generally) - Attacker knows or guesses an allowlisted name (often public — esbuild, sharp, node-sass, better-sqlite3)
- Projects that disable lifecycle scripts entirely (
ignore-scripts=true) are immune - Projects with no allowBuilds policy never relied on this gate in the first place
Trigger install — lifecycle script executes
pnpm install locally or CI runs it on PR/build. pnpm resolves the malicious package, evaluates allowBuilds against the spoofed manifest name, and executes the attacker's postinstall / preinstall script with the privileges of the install context.pnpm installis invoked- Network egress is available for second-stage payload
- Hermetic CI runners with egress allowlists limit C2
- Developer workstations rarely have egress restriction
Credential exfil and lateral movement
~/.npmrc, ~/.aws/credentials, ~/.ssh/, GITHUB_TOKEN, NPM_TOKEN, and CI-injected secrets, then exfiltrates via DNS or HTTPS to an attacker endpoint. On CI, the same tokens are typically scoped to publish back to the registry — closing the worm loop (Shai-Hulud pattern).- Tokens present in environment or filesystem
- Egress to attacker infrastructure
- Short-lived OIDC tokens (GitHub Actions, GitLab JWT) limit blast radius vs long-lived PATs
- Secret scanners on outbound traffic (Nightfall, GitGuardian inline) can catch token shapes
The supporting signals.
| In-the-wild status | No confirmed exploitation of *this specific CVE* as of 2026-07-01, but the class (lifecycle-script abuse via supply-chain) is under active mass exploitation — Shai-Hulud worm, TanStack compromise (@tanstack/* GHSA-g7cv-rxg3-hmpx), and the Microsoft-reported 33-package dependency-confusion campaign all chain on this same primitive. |
|---|---|
| Proof-of-concept | Conceptual PoC implied by the GHSA writeup; no public weaponized repo named as of disclosure. Trivial to reproduce — name field in package.json is the entire bypass. |
| EPSS | 0.0011 (~11th percentile) — low predicted exploitation in next 30 days, but EPSS lags supply-chain class bugs because telemetry is endpoint-centric, not registry-centric. |
| KEV status | Not listed. CISA KEV historically lags npm/pnpm ecosystem bugs by months. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H — AV:N is generous (the attack vector is the registry, not the network), UI:R is correct (developer must run install), AC:H reflects needing a poisoned package in the graph. |
| Affected versions | pnpm < 10.34.2 on the 10.x line; pnpm < 11.5.3 on the 11.x line. pacquet (Rust port) affected in the same window. |
| Fixed versions | pnpm 10.34.2 and pnpm 11.5.3. No distro backports — this is a Node tooling package, upgrade via npm i -g pnpm@latest or Corepack pin. |
| Exposure data | pnpm self-reports ~30% of the JavaScript build-tool market by late 2025 surveys; Shodan/Censys not applicable (no network listener). Internal CI fleets are the real exposure surface. |
| Disclosure | 2026-06-25 via GitHub Security Advisory database, coordinated with the pnpm maintainers. |
| Reporter | Credited to the pnpm security team and external GHSA submitters — see advisory for full credits. |
noisgate verdict.
Verdict held at HIGH because the decisive factor is role multiplier — the affected component runs in developer and CI build contexts where a single successful chain harvests source-tree-wide credentials and publish tokens, which is the canonical supply-chain pivot pattern. We do not upgrade to CRITICAL because exploitation still requires landing a poisoned package in the victim's dependency graph AND a developer or CI invoking pnpm install — not a network-reachable trigger.
Why this verdict
- Friction audit — entry requires supply-chain landing: attacker must first publish a malicious package the victim resolves. This is no longer a high bar in 2026 (Shai-Hulud, TanStack), but it is still not internet-reachable on its own — supports HIGH, not CRITICAL.
- Friction audit — UI:R is real:
pnpm installmust be invoked. On CI this is automatic on every PR; on workstations it's frequent. Not a wormable network primitive. - Friction audit — defense-in-depth bypass, not RCE primitive: the bug specifically defeats
allowBuilds, a *hardening control*. Projects withignore-scripts=trueare unaffected; projects with no allowBuilds policy never benefited from the gate — narrowing the at-risk population to teams who deliberately tried to harden. - Role multiplier — CI/CD build server (canonical high-value role): chain succeeds end-to-end. Blast radius: every secret in the CI environment, every artifact-registry publish token, every cloud OIDC role assumable from that runner. This is the supply-chain pivot pattern — verdict floor is HIGH.
- Role multiplier — developer workstation: chain succeeds. Blast radius: local SSH keys, gh tokens, AWS keys, browser sessions on macOS keychain. Worm potential via published packages from compromised maintainer credentials.
- Role multiplier — production runtime: rarely runs
pnpm installin production. Limited.
Why not higher?
Not CRITICAL because there is no unauthenticated network trigger and no auto-execution without a developer or CI initiating pnpm install. EPSS is low and there is no published weaponized exploit chain naming this specific CVE. A CRITICAL bucket implies hours-to-days mass exploitation pressure; the evidence here is class-level not CVE-specific.
Why not lower?
Not MEDIUM because the bug defeats a hardening control specifically designed to stop the supply-chain class that *is* being mass-exploited in 2026. The role multiplier on CI runners (token egress, publish-back worm potential) holds the verdict floor at HIGH regardless of EPSS.
What to do — in priority order.
- Set
ignore-scripts=trueglobally in CI — Configure~/.npmrcand project-level.npmrcwithignore-scripts=trueon every CI runner. This bypasses the broken allowBuilds gate entirely by refusing to run *any* lifecycle script. Apply within the noisgate HIGH mitigation SLA — 30 days. For packages that legitimately need a build step (esbuild, sharp), pre-build artifacts in a sandboxed pipeline and consume the prebuilt tarballs. - Pin pnpm via Corepack and block drift — In
package.jsonset"packageManager": "[email protected]"(or 10.34.2 for the LTS line) and enable Corepack in CI. Fail the build if the resolved pnpm version is below the fix. Deploy within 30 days alongside the upgrade rollout. - Enforce internal proxy registry with publisher allowlist — Verdaccio / Nexus / Artifactory in proxy-only mode, blocking shadowing of internal scopes by public packages, and requiring approval for any new transitive dependency publisher. Closes the upstream-landing precondition.
- Scope CI tokens to short-lived OIDC — Replace long-lived
NPM_TOKENand PATs with OIDC federation (GitHub Actions → cloud, GitHub Actions → npm publish via trusted publishers). Reduces the value of any single install-time exfil. - EDR rules for node child processes — On dev workstations and CI runners, alert on
nodespawningsh,bash,cmd.exe,curl,wget, or reading~/.aws/,~/.ssh/,~/.npmrcduring an install window. CrowdStrike, SentinelOne, Wazuh all support this.
- Lockfile pinning + integrity hashes — only protects against *substitution* of an already-resolved package; does NOT protect against the first introduction of a poisoned package or a hijacked publisher.
- SBOM generation alone — visibility without an allowlist policy does not block install-time script execution.
- Web Application Firewall — there is no HTTP request path to filter; the attack vector is the package registry resolved by the developer's machine.
pnpm audit— reports known CVEs in dependencies but does not validate manifest-vs-resolved-name identity.
Crowdsourced verification payload.
Run on every developer workstation and CI runner where pnpm is installed. Invoke as bash check-pnpm-cve-2026-55487.sh from any shell — no root required. Detects pnpm version, compares against the 10.34.2 / 11.5.3 fix lines, and flags whether ignore-scripts is set as a compensating control.
#!/usr/bin/env bash
# noisgate verification: CVE-2026-55487 (pnpm manifest identity spoofing)
# Output: VULNERABLE / PATCHED / UNKNOWN
set -u
if ! command -v pnpm >/dev/null 2>&1; then
echo "UNKNOWN: pnpm not on PATH"
exit 2
fi
VER=$(pnpm --version 2>/dev/null | tr -d '[:space:]')
if [ -z "$VER" ]; then
echo "UNKNOWN: could not read pnpm --version"
exit 2
fi
MAJOR=${VER%%.*}
REST=${VER#*.}
MINOR=${REST%%.*}
PATCH=${REST#*.}
ver_ge() {
# ver_ge A.B.C X.Y.Z -> 0 if A.B.C >= X.Y.Z
printf '%s\n%s\n' "$2" "$1" | sort -V -C
}
IGNORE_SCRIPTS=$(pnpm config get ignore-scripts 2>/dev/null | tr -d '[:space:]')
FIXED=1
case "$MAJOR" in
10)
if ver_ge "$VER" "10.34.2"; then FIXED=0; fi
;;
11)
if ver_ge "$VER" "11.5.3"; then FIXED=0; fi
;;
*)
if [ "$MAJOR" -gt 11 ] 2>/dev/null; then FIXED=0; fi
;;
esac
if [ "$FIXED" -eq 0 ]; then
echo "PATCHED: pnpm $VER (ignore-scripts=$IGNORE_SCRIPTS)"
exit 0
fi
if [ "$IGNORE_SCRIPTS" = "true" ]; then
echo "VULNERABLE: pnpm $VER (mitigated by ignore-scripts=true, but upgrade still required)"
exit 1
fi
echo "VULNERABLE: pnpm $VER (no fix, ignore-scripts not enforced)"
exit 1
If you remember one thing.
"packageManager": "[email protected]" (or 10.34.2 for teams on the LTS line) in every repo. Per the noisgate mitigation SLA for HIGH, you have ≤ 30 days to roll out a compensating control — set ignore-scripts=true on all CI runners this week as the fast win, since it neutralizes the broken allowBuilds gate entirely. Per the noisgate remediation SLA, full pnpm upgrade across the org must complete within ≤ 180 days, but CI fleets should be done within 30 because that is where token blast-radius is largest. There is no KEV-driven hours-deadline override here, but the 2026 supply-chain wave (Shai-Hulud, TanStack) means treating this as backlog is the wrong call — prioritize it above generic dev-tool CVEs.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.