You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A project can pass the pnpm-audit source with 0 findings while a vulnerable package sits on disk in node_modules. Found live while patching hexaxia.tech on 2026-09-08.
What happened
After fixing the override floors on hexaxia.tech (main and dev), verification looked complete:
pnpm audit -> 0 critical / 0 high / 0 moderate / 0 low
lockfile -> js-yaml@4.3.2 only
pnpm why -> js-yaml@4.3.2 only
cve-lite 1.29.1 disagreed:
CRITICAL (1)
OA008 js-yaml Override floor not applied - vulnerable copy still on disk
package.json > /pnpm/overrides/js-yaml
cve-lite was right. On disk:
node_modules/js-yaml/package.json -> 5.2.3 <-- real directory, dated Aug 11
node_modules/.pnpm/js-yaml@4.3.2/node_modules/js-yaml -> 4.3.2
js-yaml@5.2.3 is below the 5.x fix line (5.3.0), so it is vulnerable to GHSA-5p4m-2wfm-xmqj. It appeared in no lockfile entry.
Root cause
node_modules held a stale hoisted layer from an older install — 379 top-level entries where pnpm's isolated linker produces 38. pnpm's isolated install does not remove a pre-existing hoisted tree, so the orphaned copies persisted across every subsequent pnpm install and lockfile change.
This matters beyond dead weight: under pnpm's isolated layout, top-level node_modules/is on the Node resolution path for the project's own source. A bare require('js-yaml') from project code would have resolved to the stale 5.2.3, not the 4.3.2 the lockfile promises.
rm -rf node_modules && pnpm install --frozen-lockfile cleared it (lockfile untouched, 379 -> 38 entries). OA008 then went clean on both projects.
The gap
PnpmAuditSource inherits pnpm audit's model: the lockfile is the truth. Anything on disk but not in the lockfile is invisible to it. So HexOps would have reported these two projects fully clean while a vulnerable package was physically present and resolvable.
This is the read-path twin of #80 (which fixed false-positive success for nested deps) — same family, different surface: there we trusted a top-level version check, here we trust the lockfile.
Hoisted-layer heuristic — for a pnpm project with no node-linker=hoisted in .npmrc, a top-level entry count far exceeding the direct-dependency count is strong evidence of a stale hoisted tree. Cheap to compute, high signal.
Adopt OA008 as a source signal. cve-lite already detects this correctly and OverrideHygieneSource already runs the overrides subcommand. Worth confirming OA008 is surfaced at full weight rather than being treated as advisory-only config noise, since here it was the only scanner telling the truth.
A project can pass the pnpm-audit source with 0 findings while a vulnerable package sits on disk in
node_modules. Found live while patching hexaxia.tech on 2026-09-08.What happened
After fixing the override floors on hexaxia.tech (
mainanddev), verification looked complete:cve-lite 1.29.1 disagreed:
cve-lite was right. On disk:
js-yaml@5.2.3is below the 5.x fix line (5.3.0), so it is vulnerable to GHSA-5p4m-2wfm-xmqj. It appeared in no lockfile entry.Root cause
node_modulesheld a stale hoisted layer from an older install — 379 top-level entries where pnpm's isolated linker produces 38. pnpm's isolated install does not remove a pre-existing hoisted tree, so the orphaned copies persisted across every subsequentpnpm installand lockfile change.This matters beyond dead weight: under pnpm's isolated layout, top-level
node_modules/is on the Node resolution path for the project's own source. A barerequire('js-yaml')from project code would have resolved to the stale 5.2.3, not the 4.3.2 the lockfile promises.rm -rf node_modules && pnpm install --frozen-lockfilecleared it (lockfile untouched, 379 -> 38 entries). OA008 then went clean on both projects.The gap
PnpmAuditSourceinheritspnpm audit's model: the lockfile is the truth. Anything on disk but not in the lockfile is invisible to it. So HexOps would have reported these two projects fully clean while a vulnerable package was physically present and resolvable.This is the read-path twin of #80 (which fixed false-positive success for nested deps) — same family, different surface: there we trusted a top-level version check, here we trust the lockfile.
Proposed
node_modulesfor packagepackage.jsonfiles whosename@versionhas no lockfile entry, and surface them as findings rather than silently ignoring them. This is the concrete detector Flag divergence between committed lockfile and installed node_modules in security scan #113 describes but does not specify.node-linker=hoistedin.npmrc, a top-level entry count far exceeding the direct-dependency count is strong evidence of a stale hoisted tree. Cheap to compute, high signal.verifyAuditClearshould consider a clean reinstall before declaring an advisory cleared, so a stale on-disk copy cannot read as either a false clear or a false "still vulnerable". Remediation engine mishandles grype Go-binary findings: bumps wrapper package, leaves stale binaries, falsely reports 'findings persist' #122 already asked for prune+reinstall on the grype path for the same underlying reason; this generalises it.OverrideHygieneSourcealready runs theoverridessubcommand. Worth confirming OA008 is surfaced at full weight rather than being treated as advisory-only config noise, since here it was the only scanner telling the truth.Related
>=range overrides can silently resolve out-of-range under pnpm node-linker=hoisted #126 — bare>=overrides resolving out-of-range undernode-linker=hoisted(same hoisting failure mode)