Repository navigation
Yarn berry still drops a portal: copy whose target has no package.json (e.g. a .gitkeep placeholder) or whose package.json lacks name/version, so scans succeed and VEX attests not_affected while that copy installs unpatched #1579
Description
[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).
Summary
#1571 (fix for #1561) treats a relative portal: / link: copy as unknown when its target is missing, empty, or has a package.json that isn't JSON. Two neighbouring shapes still fail open, on both the hosted and vendored paths and in lock-only vex:
- A
portal:target that is a non-empty directory with nopackage.json. The real-world case is a placeholder directory committed with a.gitkeep, which a fetch or build step fills before the install.linked_copy_unknownexempts any non-empty directory without a manifest as "alink:to plain assets". That exemption can't apply toportal:: yarn refuses to install a portal without a manifest (YN0001 … lp2@portal:./vend/lp…: Manifest not found, on 4.0.2 and 4.18.1). So a lock that carries aportal:entry whose target has nopackage.jsonat scan time proves the target isn't what will be installed, and the copy is unknown. - A
portal:/link:target whosepackage.jsonparses but has nonameor noversion.CopyRef::identityneeds both, so it returnsNone.manifest_parsedis true, so the copy is dropped as "names no versioned package". Yarn installs such a portal happily, and in my repronode_modules/lp2holds unpatched left-pad 1.3.0 code. Thefile:twin with the same nameless manifest fails closed (redirect_copy_unpatched,vexexit 2), as Yarn classic renamed-copy detection (#1489) misses afile:copy outside the project root and a tarball whose root isn'tpackage/, so scans succeed and VEX attests while that copy installs unpatched #1516 intends.
In every failing cell the hosted and vendored scans report success with no copy warning, and both in-run and lock-only VEX attest not_affected for pkg:npm/left-pad@1.3.0. After that, yarn install --immutable (with the target populated) puts patched bytes in node_modules/left-pad and unpatched bytes in node_modules/lp2.
Impact
These are false not_affected VEX statements and clean scans for a tree that ships an unpatched copy of the patched package@version. It's the same fail-open class as #1516, #1540 and #1561.
Repro (Linux, real yarn 4.18.1 and 4.0.2 from @yarnpkg/cli-dist)
# project: {"dependencies":{"left-pad":"1.3.0","lp2":"portal:./vend/lp"}}, .yarnrc.yml: nodeLinker: node-modules
mkdir -p vend && npm pack left-pad@1.3.0 && tar xzf left-pad-1.3.0.tgz && mv package vend/lp
yarn install # lock: "lp2@portal:./vend/lp::locator=app%40workspace%3A." version 0.0.0-use.local, linkType: soft
# Shape 1: lock-only checkout, the portal target is a committed placeholder
mkdir ../co && cp package.json yarn.lock .yarnrc.yml ../co/ && mkdir -p ../co/vend/lp && touch ../co/vend/lp/.gitkeep
socket-patch scan --mode hosted --json --yes --cwd ../co --api-url $API --org org --api-token x --vex ../co/out.vex.json
# exit 0, status success, applied 1, no redirect_copy_unpatched; VEX: 1 statement not_affected
socket-patch vex --cwd ../co --api-url $API --org org --api-token x --patch-server-url $API -O ../co/lo.vex.json
# exit 0, not_affected
(cd ../co && yarn install --immutable) # with only .gitkeep: YN0001 "Manifest not found", so yarn can't install this as-is
rm -rf ../co/vend && cp -r vend ../co/ && (cd ../co && yarn install --immutable) # the fetch step ran: Done
head -c 22 ../co/node_modules/lp2/index.js # unpatched left-pad 1.3.0
head -c 22 ../co/node_modules/left-pad/index.js # /* SOCKET-PATCHED */
# Shape 2: target present, package.json without "name" (or without "version")
python3 -c "import json;p='vend/lp/package.json';d=json.load(open(p));d.pop('name');json.dump(d,open(p,'w'))"
# same scan / vex: exit 0, success, not_affected; yarn install --immutable: Done, node_modules/lp2 unpatchedFor the mock I used a Python stand-in for the patch API: batch, by-package, view, patches/package with tarball (real sha512) and yarn-berry-zip (bootstrapped 10c0 checksum) artifacts, and the tarball route. The patch prepends a marker to index.js.
Expected vs actual
docs/ecosystems.md ("yarn copies under another dependency name") says: "a portal: / link: copy whose package.json isn't read … has no version either, so it may be any version of any package and vex attests nothing that lock wires. A link: to a non-empty directory with no package.json is not a package copy." It also says "A copy whose package can't be read … is treated as possibly any package".
- Expected: a
portal:copy whose target has nopackage.jsonis unknown, since the exemption is documented only forlink:, and yarn can't install a manifest-less portal anyway. A linked copy whose manifest names no package or no version is also unknown, matching thefile:directory twin. Hosted warnsredirect_copy_unpatched, vendored warnsvendor_copy_unpatched, andvexwithholds the lock's packages. - Actual: the copy is dropped. Scans exit 0 with
success, and every VEX path attestsnot_affected.
Matrix (Linux, main 2c82c77)
| Cell | yarn 4.0.2 | yarn 4.18.1 |
|---|---|---|
portal: target = dir with only .gitkeep, hosted scan --vex + lock-only vex |
fail | fail ×2 |
same, scan --mode vendored --vex + vex |
fail | fail |
portal: target package.json without name, hosted + vex |
fail | fail ×2 |
portal: target package.json without version, hosted + vex |
fail | fail ×2 |
same two, vendored + vex |
– | fail |
link: target without name / version (hosted + vex) |
fail | fail |
link: target with only .gitkeep |
dropped (documented as no package copy) | same |
#1561 shapes (missing, empty, LFS pointer), portal: hosted / vendored / vex |
pass (fixed) | pass (fixed) |
Control: file:./vend/lp with nameless package.json |
– | pass (fails closed) |
macOS and Windows weren't probed. The code path is OS-independent.
First bad
This has been present since berry renamed-copy detection landed (#1489). #1551 and #1571 narrowed it but left these shapes.
Suspect code
crates/socket-patch-core/src/vendor/yarn_copies.rs:143-163(linked_copy_unknown): the "non-empty directory, nopackage.json" exemption applies toportal:as well aslink:.CopyRef::Linkeddoesn't record which protocol it came from, so the two can't be told apart here. Also,if manifest_parsed { return false; }treats a parsed manifest withoutname/versionas "no package copy".crates/socket-patch-core/src/formats/yarn/copy.rs:73(CopyRef::identity,Linkedarm): needs bothnameandversion, elseNone.crates/socket-patch-core/src/vex/discover/yarn.rs:901(record_copies) andvendor/yarn_copies.rs:118share the decision, so hosted, vendored andvexall fail the same way.
Activity
mikolalysenko commentedon Oct 15, 2026
[agent] Triage:
priority:p1(Yarn Berry). Not a duplicate: it is a follow-up to #1561 / #1571 covering two shapes that fix left open (aportal:target with nopackage.json, and a linked manifest withoutname/version), inlinked_copy_unknown/CopyRef::identity. No open PR covers it yet. Same fail-open class as #1516 and #1540, which are closed.Generated by Claude Code
mikolalysenko commentedon Oct 15, 2026
[agent] Claiming this issue (shared root cause:
linked_copy_unknown/CopyRef::identitydrop aportal:copy with no manifest, and a linked copy whose manifest lacks name/version, instead of treating it as unknown). Branch: agent/fix-yarn-portal-copy-unknown. Claim-ID: 2026-10-15T18:21:08Z-a649c7Generated by Claude Code
mikolalysenko commentedon Oct 15, 2026
[agent] Draft PR: #1586
Generated by Claude Code
Flag yarn portal copies that can't be identified
Name versionless yarn copies by their package