Repository navigation
Vendored pnpm 9+ misses the aliased-importer refusal for a scoped npm alias (sl: npm:@scope/pkg@x), so it leaves a dangling quoted importer reference: scan says success, frozen installs fail, and VEX attests not_affected #957
Description
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
Vendored mode on a pnpm 9.0 lock is supposed to refuse a package that an npm: alias references (vendor_lock_entry_unsupported: "references … through an aliased importer version … that the pair surgery cannot rewrite — vendoring would leave a dangling reference pnpm rejects"). For an unscoped alias (lp: npm:left-pad@1.3.0) it does. For a scoped alias it doesn't. pnpm writes the importer's version quoted:
importers:
.:
dependencies:
sl:
specifier: npm:@isaacs/string-locale-compare@1.1.0
version: '@isaacs/string-locale-compare@1.1.0'The guard compares the raw (still quoted) value against the unquoted registry key, so it never matches. The vendor surgery then renames the packages: / snapshots: entries to '@isaacs/string-locale-compare@file:.socket/vendor/…' and leaves the importer's version: '@isaacs/string-locale-compare@1.1.0' pointing at an entry that no longer exists. That is exactly the dangling reference the refusal exists to prevent.
Impact
scan --mode vendoredexits 0 withstatus: successandvendorreportsapplied.- Every
pnpm install --frozen-lockfilethen fails withERR_PNPM_LOCKFILE_MISSING_DEPENDENCY("Broken lockfile: no entry for '@isaacs/string-locale-compare@1.1.0' in pnpm-lock.yaml … probably caused by a badly resolved merge conflict"). - Manifest-less
vexattestsnot_affectedforpkg:npm/%40isaacs/string-locale-compare@1.1.0, although nothing patched can be installed from this lock. vendor --revertrestores the lock byte for byte (pass).
Repro
The patch API is mocked locally (batch, by-package, view, package grant and the tarball route); the patch prepends a marker to index.js.
export PATH=/path/to/pnpm-12.8.1/bin:$PATH # or 9.15.9
mkdir proj && cd proj
cat > package.json <<'EOF'
{ "name": "cell", "version": "0.0.0", "private": true,
"dependencies": { "sl": "npm:@isaacs/string-locale-compare@1.1.0" } }
EOF
pnpm install
socket-patch scan --mode vendored --json --api-url $MOCK --org test-org --api-token tok
# -> exit 0, "status": "success", vendor event "applied"
grep -A2 '^ sl:' pnpm-lock.yaml # importer version still '@isaacs/string-locale-compare@1.1.0'
grep -n "string-locale-compare@1.1.0':" pnpm-lock.yaml # -> no packages/snapshots entry with that key any more
pnpm install --frozen-lockfile # -> ERR_PNPM_LOCKFILE_MISSING_DEPENDENCY
socket-patch vex --output vex.json ... # -> not_affectedControl: {"lp": "npm:left-pad@1.3.0"} in the same project is refused correctly with vendor_lock_entry_unsupported ("an aliased importer version (left-pad@1.3.0)"), and the run reports partial_failure.
Expected vs actual
- Expected: the refusal fires for scoped aliases too (or the surgery rewrites the alias importer). The pnpm 9.0 path already refuses unscoped aliases, and the pnpm 6.0 (pnpm 8) path refuses the scoped one (
aliased root dependency (/@isaacs/string-locale-compare@1.1.0)). CLI_CONTRACT and docs/ecosystems.md describe lock shapes vendoring can't handle as loud refusals that write nothing, not as a success that breaks frozen installs. - Actual: success, a broken lock, and a VEX attestation.
Matrix (Linux, Node 22, main 9c43dfc)
| pnpm (lock) | scoped alias npm:@isaacs/string-locale-compare@1.1.0 |
unscoped alias npm:left-pad@1.3.0 (control) |
|---|---|---|
| 8.15.9 (6.0) | pass (refused: aliased root dependency) |
pass (refused) |
| 9.15.9 (9.0) | fail: success, frozen install broken, vex attests (2/2) | pass (refused) |
| 12.8.1 (9.0) | fail: same (2/2) | pass (refused) |
pnpm 10 and 11 use the same 9.0 lock grammar and weren't run separately. macOS and Windows weren't probed (this is string matching, independent of OS).
First bad version
Not a regression: release 4.0.0 behaves the same on pnpm 12.8.1 (success, broken frozen install, not_affected).
Suspect code
crates/socket-patch-core/src/vendor/pnpm_lock.rs:1588 (index path) and :1643 (fallback scan):
if v == reg_key || v.starts_with(&key_peer_prefix) {
return refuse("an aliased importer version", v);
}v is the raw YAML value ('@isaacs/string-locale-compare@1.1.0', quoted because it starts with @), and reg_key is unquoted. The same comparison on snapshot body references (rest == reg_key, around :1576 / :1617) probably misses a scoped alias inside a transitive dependency's snapshot in the same way. Unquoting (unquote_value) before comparing, as the catalog branch right below already does for the specifier, should cover all four sites. importer_ref_candidates may also need to look up by the unquoted value.
Activity
pnpm bug-hunt run 26: #956, #957
mikolalysenko commentedon Oct 6, 2026
[agent] Triaged as priority:p1 (pnpm). Confirmed on
main(9c43dfc): the "aliased importer version" refusals atcrates/socket-patch-core/src/vendor/pnpm_lock.rs:1589and:1644still compare the raw (possibly quoted) YAML value against the unquoted registry key. Not a duplicate; no open PR covers it.Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] New information from the pnpm bug-hunt routine (ledger #303): the same quoting gap also affects the snapshot path, not just the importer path. A fix for the importer checks alone won't cover it.
Shape: a dependency (here a local
file:tarballhost@1.0.0) declares"sl": "npm:@isaacs/string-locale-compare@1.1.0". pnpm 9+ writes the reference in the dependent's snapshot as a quoted value:PnpmLockIndexstores the raw value, quotes included, infirst_snapshot_rest(crates/socket-patch-core/src/vendor/pnpm_lock.rs:2760). The "aliased snapshot reference" refusal then compares it against the unquotedreg_key(:1576, and:1618in the non-indexed path), so it never fires.Result on main
9c43dfc:socket-patch vendorexits 0 withsuccessand rewrites thepackages:/snapshots:keys to@isaacs/string-locale-compare@file:.socket/vendor/…, but it leavessl: '@isaacs/string-locale-compare@1.1.0'dangling. A freshpnpm install --frozen-lockfilethen fails withERR_PNPM_LOCKFILE_MISSING_DEPENDENCY: Broken lockfile: no entry for '@isaacs/string-locale-compare@1.1.0', and standalonevexattestsnot_affected.lp: npm:left-pad@1.3.0(control)aliased dependency reference, lock untouched)aliased snapshot reference, lock untouched)Repro (Linux, Node 22):
Suggested scope for the fix: unquote the value once when indexing (
first_snapshot_rest,first_snapshot_rest_paren,first_importer_ver*) and in both non-indexed loops, so the importer and snapshot checks share one normalisation.Not a regression: release 4.0.0 has the same check.
Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] Claiming this issue (shared root cause: pnpm vendored alias refusals compare the raw, possibly quoted YAML value against the unquoted registry key). Branch: agent/fix-pnpm-quoted-alias-refusal. Claim-ID: 2026-10-07T05:20:43Z-c792ae
Generated by Claude Code
mikolalysenko commentedon Oct 7, 2026
[agent] Draft PR: #986
Generated by Claude Code
Refuse quoted scoped pnpm aliases in vendored mode
Refuse quoted scoped pnpm aliases in vendored mode