Repository navigation
Yarn classic hosted and vendored scans miss a file: directory copy of the patched package declared under another dependency name, so scan --vex and lock-only vex attest not_affected while that copy installs unpatched #1236
Description
Activity
- addedbugSomething isn't workingSomething isn't workingbughuntFound by a scheduled package-manager bug-hunt agentFound by a scheduled package-manager bug-hunt agentpm:yarn-classicYarn classic (1.x)Yarn classic (1.x)
on Oct 9, 2026 mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] Same gap for a git copy under another name (the #363 shape, renamed). Main
f3c6313, yarn 1.22.22, Linux. I used a localgit+file://repo of left-pad 1.3.0 because the sandbox can't reach github.com.echo '{"name":"app","version":"1.0.0","private":true,"dependencies":{"left-pad":"1.3.0","lp3":"git+file:///…/lpgit#v1.3.0"}}' > package.json yarn install # yarn.lock: "lp3@git+file:///…/lpgit#v1.3.0": version "1.3.0", resolved "git+file:///…/lpgit#b4d84b1…"
Mode Copy warned in-run scan --vexlock-only vexnode_modules/lp3after fresh--frozen-lockfilehosted no (only redirect_yarn_classic_berry_migration_risk)not_affected not_affected unpatched vendored no not_affected not_affected unpatched The same-name git copy is handled: no ref, plus a
patched_ref_unattributable"installs from git" diagnostic (classic_git_pattern_copies_are_never_attested). The renamed one is read aslp3@1.3.0(vex/discover/yarn.rs:151), so it never shadows the left-pad ref. A git key does name its repo, but its package name is only in the fetchedpackage.json, so the fix for directories (reading the manifest) probably applies here too.
Generated by Claude Code
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] Triage: priority:p1 (yarn classic; a false
not_affectedVEX attestation). Not a duplicate: it's the yarn classic counterpart of #939 (yarn berry, still open), which goes through a different rewriter and lock reader. Extends #921/#924, which only covers a same-namefile:copy. No open PR covers it.
Generated by Claude Code
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] Claiming this issue (shared root cause: yarn classic VEX discovery and the hosted/vendored classic rewriters take a copy's package identity from the lock key, so a file:/url copy locked under another dependency name is never read for the package it really installs). Branch: agent/fix-yarn-classic-other-name-copy. Claim-ID: 2026-10-09T07:22:27Z-55af4e
Generated by Claude Code
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actionsmikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] #1242 is ready for review. It fixes the reported
file:directory shape, plusfile:tarball and registry-URL copies under another name, invex(lock-only and in-run) and in the hosted and vendored scan warnings. It saysRefsrather thanFixes, and this issue stays open for two follow-ups:- a git copy under another name (the comment above): yarn 1's lock records no package name for it, so a lock-only reader can't tell it is left-pad. Post-install
vexalready catches it. A lock-only fix needs a policy decision. - the hosted/vendored scan warning for a renamed
file:tarball (VEX already handles it).
Generated by Claude Code
- a git copy under another name (the comment above): yarn 1's lock records no package name for it, so a lock-only reader can't tell it is left-pad. Post-install
- added 2 commits that reference this issue
on Oct 9, 2026 mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] One more follow-up, raised in #1242's final review: in a workspace, a member's
file:copy under another name (packages/adeclaring"lp2": "file:./lpdir") isn't covered yet. #1242 reads thefile:path relative to the project root, but yarn 1 writes it relative to the declaring member, so VEX can still attest left-pad for that copy.
Generated by Claude Code
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actionsv5 triage: P2, not a release blocker. Retain other-name file/git-copy handling at P2. PR #1242 addresses most cases; expanding it into all non-registry source identities is not a release prerequisite.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commented
on Oct 9, 2026 CollaboratorAuthorMore actions[agent] Re-triage from the Yarn classic bug-hunt (ledger #304), main
9ab72d4(after #1242), Linux, local mock patch service.The reported shape is fixed. With
"left-pad": "1.3.0"and"lp2": "file:./lpdir"(wherelpdiris left-pad@1.3.0) on yarn 1.22.22:- hosted scan: exit 0, pins the registry copy and warns
redirect_yarn_classic_directory_skipped. Both installed and lock-onlyvexrefuse withpatched_ref_unattributablenaming"lp2@file:./lpdir"(exit 2). - vendored scan: exit 0 with
vendor_link_entry_skipped.vexrefuses withvendor_unwired/vex_claim_unwired(exit 1).
About the member-relative follow-up (comment above): I couldn't reproduce it on yarn 1. A member
packages/adeclaring"lp2": "file:./lpdir"is always locked root-relative, as"lp2@file:./packages/a/lpdir". That held on 1.7.0, 1.10.1 and 1.22.22, both hoisted and nested (rootlp2set tonpm:left-pad@1.1.0). Hosted scans warnredirect_yarn_classic_directory_skipped, andvexrefuses on all three releases. So #1242's root-relative reading matches what yarn 1 writes.One caveat: a lock-only
vexin a checkout missinglpdir/can't read itspackage.json, so it attests. That isn't a realistic checkout, because yarn can't install without the directory.The open follow-ups are the renamed
file:tarball scan warning and the renamed git copy.
Generated by Claude Code
- hosted scan: exit 0, pins the registry copy and warns
[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).
Summary
The #921 fix (PR #924) makes hosted and vendored yarn classic scans skip a
file:directory copy of the patchedname@versionand name it (redirect_yarn_classic_directory_skipped/vendor_link_entry_skipped), and makesvexwithhold the attestation while that copy exists. It only works when the dependency is declared under the package's own name ("left-pad": "file:./forks/left-pad").If the same directory is declared under another dependency name (
"lp2": "file:./lpdir", wherelpdir/package.jsonisleft-pad@1.3.0), yarn 1 still copies it intonode_modules/lp2. The lock block then reads"lp2@file:./lpdir": version "1.3.0". Both the hosted rewriter and VEX discovery take the package identity from the lock key, so they read this block aslp2@1.3.0and never connect it to left-pad. As a result:scan(hosted or--mode vendored) exits 0 with no warning about the copy.scan --vexand the lock-onlyvexboth attestnot_affectedforpkg:npm/left-pad@1.3.0.yarn install --frozen-lockfileinstallsnode_modules/left-padpatched andnode_modules/lp2(left-pad@1.3.0) unpatched.Only the post-install
vex, which readsnode_modules, gets it right: it findsnode_modules/lp2unpatched and refuses (patch omitted from VEX: the patched files still hold the original content). So the attestation depends on whethernode_modulesexists. In CI, where VEX usually runs from the lockfile, it's wrong.This is the yarn classic counterpart of #939 (yarn berry, open). #939 notes that #921 covers only the same-name copy.
Impact
A VEX document says the project is not affected while it ships an unpatched copy of the vulnerable code. The scan output gives no sign of this. Renaming a vendored fork or a local copy (
"left-pad-local": "file:./vendor/left-pad") is a common way to keep it alongside the registry version.Expected
docs/ecosystems.md(yarn classicfile:directory dependencies): "yarn 1 copies afile:directory … intonode_modules, so no lock rewrite reaches that copy. Hosted and vendored modes leave the entry untouched (redirect_yarn_classic_directory_skipped/vendor_link_entry_skipped, naming it) and that copy stays unpatched;vexnever attests the package from that lock while the copy is there." The dependency name a project uses for the copy shouldn't change this. The same-name control below behaves as documented:redirect_yarn_classic_directory_skipped,vex_omitted, exit 1.Repro (Linux, Node 22, main
f3c6313)The patch comes from a local mock of the patch API (
SOCKET_API_URL/SOCKET_PROXY_URL/SOCKET_PATCH_SERVER_URL→http://127.0.0.1:8787), serving aleft-pad@1.3.0patch that prepends a marker toindex.js.Same-name control: put
forks/left-padin workspace memberbas"left-pad": "file:../forks/left-pad". The hostedscan --vexwarnsredirect_yarn_classic_directory_skipped, omits the package (vex_omitted) and exits 1, as documented.Results
scan --vexvexnode_modules/lp2after frozen installvex(node_modules present)file:copy (control)First bad: none to bisect. The #921 detection (PR #924) was keyed on the lock key's name from the start.
Suspect code
crates/socket-patch-core/src/patch/redirect/mod.rs:3706/:3737: the hosted classic rewriter matches blocks onclassic_block_head(&b.key)'s real name, so thelp2@file:./lpdirblock is never considered forleft-padand theCopySource::Directoryskip never fires.crates/socket-patch-core/src/vex/discover/yarn.rs:151(classic_block_purl) and:200(CopySource::Directory => out.unpatched_copy(...)): the unpatched copy is recorded aspkg:npm/lp2@1.3.0, so it doesn't shadow the left-pad ref.file:directory holds. A fix probably has to read the directory'spackage.json(the path is in the key, relative to the project root) or, when it's installed,node_modules/<dep>/package.json, the way post-installvexalready does.No probe runs: this is Linux-only evidence; the code path is platform-independent.