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
Agent-mode apply skips a bundled copy inside another vlt/pnpm store entry whenever the package is also installed normally, and VEX attests not_affected #601
[agent] Found by the scheduled vlt bug-hunt routine (ledger #307).
Summary
A package can bundle a vulnerable name@version (bundleDependencies) while the project also installs that same name@version normally. vlt unpacks the bundled copy into the parent's store entry, at node_modules/.vlt/~npm~bundler@1.0.0/node_modules/bundler/node_modules/left-pad. Agent mode (scan --mode agent, apply) patches only the normal store copy (.vlt/~npm~left-pad@1.3.0/...) and leaves the bundled copy pristine. It reports success, and socket-patch vex attests not_affected / inline_mitigations_already_exist. The parent package then loads the unpatched bytes at runtime.
If the bundled copy is the only install (the project doesn't depend on left-pad directly), agent mode patches it. So the crawler can find the copy. It just stops looking once it has found any copy.
The same thing happens on pnpm 10.28.0 (.pnpm/bundler@1.0.0/node_modules/bundler/node_modules/left-pad stays pristine, vex exits 0), since both use the same crawler path. Hosted and vendored are fine since #472: they warn redirect_vlt_bundled_instance_skipped / vendor_bundled_instance_skipped, and vex refuses to attest.
Impact
The VEX statement is false: the shipped tree still contains an unpatched copy of the vulnerable version that a dependency actually requires. apply exits 0 with already_patched on re-runs, so there's nothing that warns the user.
Repro (vlt 1.3.5, Linux, main b1f9818, reproduced twice on each version)
The mock registry serves left-pad@1.3.0 (index.js = module.exports = 'pristine') and bundler@1.0.0, which bundles left-pad@1.3.0 and has main: module.exports = require('left-pad'). The mock patch API's patch changes index.js to 'patched'. This is the same mock the ledger uses.
Control: drop "left-pad":"1.3.0" from dependencies. After the agent scan, the bundled copy reads 'patched'.
Expected vs actual
Expected: agent apply patches every installed physical copy of the purl. The crawler comments say as much ("a target can physically exist ONLY inside another package's store entry … leaving an installed, scan-visible package invisible to apply (fail-open)"), and so does Fix agent vex checking only one installed copy (#516) #517 ("agent vex checking only one installed copy"). If it can't patch a copy, it should at least not attest it. CLI_CONTRACT.md "Contested locks" already says a bundled copy of the same name@version must not be attested.
Actual: the bundled copy stays unpatched, apply and scan exit 0, and vex says not_affected.
OS
vlt 1.0.10
vlt 1.2.0
vlt 1.3.5
pnpm 10.28.0
Linux
reproduces
reproduces
reproduces
reproduces
macOS / Windows
untested (probe branches blocked)
untested
untested
untested
The bundled-only control passes on every version above.
Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:1235: find_by_purls runs the unfiltered pass 2 (which probes every store entry, including a host package's bundled node_modules) only if !pending.is_empty(), meaning only for targets that pass 1 didn't find at all. Pass 1 filters store entries by advertised name (pending_store_entries, and unmatched_names in resolve_pending_targets, around line 1318). So once the normal ~npm~left-pad@1.3.0 entry matches, the ~npm~bundler@1.0.0 entry is never probed, and agent vex sees the same incomplete copy set.
Not bisected: 4.0.0 predates vlt support, and the pass-2 fallback is newer than that.
[agent] Found by the scheduled vlt bug-hunt routine (ledger #307).
Summary
A package can bundle a vulnerable
name@version(bundleDependencies) while the project also installs that samename@versionnormally. vlt unpacks the bundled copy into the parent's store entry, atnode_modules/.vlt/~npm~bundler@1.0.0/node_modules/bundler/node_modules/left-pad. Agent mode (scan --mode agent,apply) patches only the normal store copy (.vlt/~npm~left-pad@1.3.0/...) and leaves the bundled copy pristine. It reports success, andsocket-patch vexattestsnot_affected/inline_mitigations_already_exist. The parent package then loads the unpatched bytes at runtime.If the bundled copy is the only install (the project doesn't depend on left-pad directly), agent mode patches it. So the crawler can find the copy. It just stops looking once it has found any copy.
The same thing happens on pnpm 10.28.0 (
.pnpm/bundler@1.0.0/node_modules/bundler/node_modules/left-padstays pristine, vex exits 0), since both use the same crawler path. Hosted and vendored are fine since #472: they warnredirect_vlt_bundled_instance_skipped/vendor_bundled_instance_skipped, andvexrefuses to attest.Impact
The VEX statement is false: the shipped tree still contains an unpatched copy of the vulnerable version that a dependency actually
requires.applyexits 0 withalready_patchedon re-runs, so there's nothing that warns the user.Repro (vlt 1.3.5, Linux, main
b1f9818, reproduced twice on each version)The mock registry serves
left-pad@1.3.0(index.js=module.exports = 'pristine') andbundler@1.0.0, which bundlesleft-pad@1.3.0and hasmain: module.exports = require('left-pad'). The mock patch API's patch changesindex.jsto'patched'. This is the same mock the ledger uses.Control: drop
"left-pad":"1.3.0"fromdependencies. After the agent scan, the bundled copy reads'patched'.Expected vs actual
name@versionmust not be attested.The bundled-only control passes on every version above.
Suspect code
crates/socket-patch-core/src/crawlers/npm_crawler.rs:1235:find_by_purlsruns the unfiltered pass 2 (which probes every store entry, including a host package's bundlednode_modules) onlyif !pending.is_empty(), meaning only for targets that pass 1 didn't find at all. Pass 1 filters store entries by advertised name (pending_store_entries, andunmatched_namesinresolve_pending_targets, around line 1318). So once the normal~npm~left-pad@1.3.0entry matches, the~npm~bundler@1.0.0entry is never probed, and agentvexsees the same incomplete copy set.Not bisected: 4.0.0 predates vlt support, and the pass-2 fallback is newer than that.