Repository navigation
Yarn berry treats a portal:/link:/file: copy whose package.json has an empty or invalid name ("" or "../x") as a different package, so scans succeed and VEX attests not_affected while that copy installs unpatched (#1579 follow-up) #1598
Description
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
#1586 (fixes #1579) says "an empty or invalid name in that manifest names no package, so the copy is treated as possibly any package rather than as a copy of a package called """. That holds only when the manifest also has no version. When the copy's package.json has "version": "1.3.0" and "name": "" (or an invalid npm name such as "../x" or " left-pad"), CopyRef::identity returns ("", "1.3.0"). The copy is then read as some other package and skipped. Hosted and vendored scans exit 0 with success, and the in-run, lock-only and post-install VEX all attest not_affected, while yarn installs that copy (node_modules/lp2) with unpatched left-pad code.
The same happens for a file: directory copy with "name": "", because manifest_name (formats/yarn/source.rs:131) returns Some(""). The nameless file: control fails closed correctly.
Related: an empty "version": "" on a portal: copy named left-pad is also read as "another version" and attests. That is the same class: no usable version should mean "may be any version", as #1586 already does for a missing version.
Impact
This is a false not_affected attestation, the same fail-open class as #1516, #1540, #1561 and #1579. A vulnerable copy ships while socket-patch reports it as patched.
Repro (Linux, yarn 4.18.1 from @yarnpkg/cli-dist, node-modules linker, local mock of the patch API)
mkdir -p app/vend/lp && cd app
# a copy of left-pad@1.3.0 whose manifest has an empty name
cp -r <left-pad@1.3.0 package dir>/. vend/lp/
python3 -c "import json;p=json.load(open('vend/lp/package.json'));p['name']='';json.dump(p,open('vend/lp/package.json','w'))"
echo '{"name":"app","version":"0.0.0","private":true,"dependencies":{"left-pad":"1.3.0","lp2":"portal:./vend/lp"}}' > package.json
printf 'nodeLinker: node-modules\nenableGlobalCache: false\n' > .yarnrc.yml
yarn install
socket-patch scan --mode hosted --json --yes --vex out.vex.json # (mock: --api-url/--org/--api-token/--patch-server-url)
# -> exit 0, status "success", applied 1, vex statements 1
# fresh checkout of the committed files:
yarn install --immutable # exit 0
head -c 20 node_modules/lp2/index.js # "/* This program is f" (unpatched)
head -c 20 node_modules/left-pad/index.js # "/* SOCKET-PATCHED */"
socket-patch vex -O post.vex.json # exit 0, 1 statement not_affectedSwap portal: for link: or file:, or the name for "../x", and the result is the same. Drop the name key instead, and the scan correctly fails closed (redirect_copy_unpatched, exit 1, lock-only vex exit 2).
Expected vs actual
- Expected: per Fix yarn portal copies read as no package (#1579) #1586 and docs/ecosystems.md ("A
portal:/link:copy … only its package.json says what it installs"), a manifest that names no valid package can't be identified, so the copy must be flagged as an unpatched copy: hosted warnsredirect_copy_unpatched, vendored warnsvendor_copy_unpatched, and VEX holds back, exactly as for a nameless manifest. CLI_CONTRACT's VEX rule is thatnot_affectedis attested only for a patch that is actually applied to what gets installed. - Actual: exit 0
success, and every VEX path attestsnot_affected.
Matrix (main 657fce2, Linux)
| Shape | yarn 4.0.2 | yarn 4.18.1 |
|---|---|---|
portal: + "name": "", hosted (scan, lock-only vex, post-install vex) |
fails | fails (×2 real installs) |
portal: + "name": "../x" / " left-pad", hosted |
untested | fails |
portal: + "version": "", hosted |
fails | fails |
link: + "name": "", hosted |
untested | fails |
file: dir + "name": "", hosted + post-install vex |
untested | fails (×2 real installs) |
portal: + "name": "" / "../x", vendored (lock-only checkout) |
untested | fails (exit 0, vex attests) |
Control: name key missing (portal or file), or .gitkeep-only portal |
pass (fails closed) | pass (fails closed) |
macOS and Windows weren't probed (the ledger records the probe-branch blocker); the code path is OS-independent.
First bad version: not a regression. Before #1586 the nameless shape failed open too; #1586 closed it only for a missing key.
Suspect code
crates/socket-patch-core/src/formats/yarn/copy.rs:84-86(CopyRef::identity,Linked): returnsSome((name, version))for any string name/version, including""or a name that failsis_safe_npm_name.linked_name(:101) filters invalid names, but it's consulted only whenidentityreturnsNone.crates/socket-patch-core/src/formats/yarn/source.rs:131(manifest_name): returnsSome(""), soDirectory/Tarballcopies with an empty name are skipped too (vendor/yarn_copies.rs:116-118Some(_) => continue; the same decision invex/discover/yarn.rs:893).manifest_nameis shared with yarn classic copy detection, so classicfile:copies may have the same gap (not tested here).
Activity
mikolalysenko commentedon Oct 16, 2026
[agent] Triaged as p1 (yarn berry, false
not_affected). Confirmed on main657fce2:CopyRef::identity(formats/yarn/copy.rs,Linkedarm) returns any stringname/versionunvalidated, andmanifest_name(formats/yarn/source.rs) returnsSome(""), so onlylinked_nameappliesis_safe_npm_name. No open PR covers it.Generated by Claude Code
mikolalysenko commentedon Oct 16, 2026
[agent] Claiming this issue (shared root cause: yarn copy identity accepts an empty or invalid manifest name/version as a real identity). Branch: agent/fix-yarn-copy-invalid-identity. Claim-ID: 2026-10-16T08:20:49Z-827f73
Generated by Claude Code
mikolalysenko commentedon Oct 16, 2026
[agent] Draft PR: #1605
Generated by Claude Code
Fix yarn copies with invalid manifest names
Use npm's own name and version rules for copies
Test #1598 copy naming through the real binary