Skip to content

Yarn berry hosted and vendored scans miss a file:/URL copy of the patched package locked under another dependency name, so lockfile VEX (and vendored VEX after install) attests not_affected while that copy installs unpatched #939

Description

[agent] Found by the scheduled Yarn Berry (2+) bug-hunt routine (ledger #305).

Summary

A yarn 4 workspace has member a that depends on left-pad@^1.3.0 from the registry. Member b depends on a copy of the same left-pad@1.3.0 under another dependency name: "lp2": "file:../../forks/left-pad-1.3.0.tgz", a file: directory, or the registry tarball URL https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz. Yarn locks that copy as lp2@file:… / lp2@https://…, with version: 1.3.0, and installs it as node_modules/lp2, whose package.json says left-pad@1.3.0.

  • scan --mode hosted and scan --mode vendored pin only the registry entry. Both exit 0 with status success and no warning about the lp2 copy.
  • A lockfile-only vex then attests pkg:npm/left-pad@1.3.0 as not_affected (inline_mitigations_already_exist) in both modes.
  • After a real fresh yarn install --immutable, node_modules/lp2/index.js is the unpatched upstream file. Hosted vex is honest at this point (not_applied, exit 1). Vendored vex still attests not_affected, exit 0, with only vendored_tree_out_of_sync ("re-run your package manager's install to resync it"). Re-running the install can't fix this, because the lp2 copy never goes through the resolutions pin.

When the copy uses the same name ("left-pad": "file:…" in member b), both modes handle it correctly. Hosted refuses with redirect_yarn_berry_unsupported_protocol + redirect_yarn_berry_shared_descriptor, and vendored refuses with vendor_override_conflict. The gap is only that every guard keys on the lock entry's ident (left-pad@…). For a non-registry protocol, yarn takes the ident from the dependency name (lp2), even though the installed package is left-pad@1.3.0.

This is the berry side of #935 (pnpm), #921 (yarn classic) and #497 (bun). Those issues cover a same-name copy, which berry already refuses. For berry, only the other-name copy gets through.

Impact

The VEX document (the CI / compliance artefact) says the product isn't affected, while the product ships an unpatched copy of exactly the patched name@version. An SCA scanner reading node_modules/lp2/package.json flags it as left-pad@1.3.0. Neither scan says anything about that copy.

Repro (yarn 4.18.1, main 9c43dfc)

# Mock patch API on :8790 serving pkg:npm/left-pad@1.3.0 (batch, by-package, view, patches/package with a
# tarball + yarn-berry-zip/yarnBerry10c0 artifact, and the hosted tarball), same contract as
# crates/socket-patch-cli/tests/e2e_redirect_yarn_berry_build.rs. Patched tgz = upstream with a marker line on index.js.
mkdir -p proj/packages/a proj/packages/b proj/forks && cd proj
npm pack left-pad@1.3.0 && mv left-pad-1.3.0.tgz forks/          # unmodified upstream
echo '{"name":"root","version":"1.0.0","private":true,"workspaces":["packages/*"]}' > package.json
echo '{"name":"a","version":"1.0.0","dependencies":{"left-pad":"^1.3.0"}}' > packages/a/package.json
echo '{"name":"b","version":"1.0.0","dependencies":{"lp2":"file:../../forks/left-pad-1.3.0.tgz"}}' > packages/b/package.json
printf 'nodeLinker: node-modules\n' > .yarnrc.yml
yarn install
socket-patch scan --mode hosted --json --yes --api-url http://127.0.0.1:8790 --org org --api-token x --patch-server-url http://127.0.0.1:8790
#   exit 0, status success, redirect.warnings: []
# fresh checkout (no node_modules):
socket-patch vex --json --output vex.json --patch-server-url http://127.0.0.1:8790 --api-url http://127.0.0.1:8790 --org org --api-token x
#   exit 0: pkg:npm/left-pad@1.3.0 not_affected (inline_mitigations_already_exist)
YARN_ENABLE_IMMUTABLE_INSTALLS=1 yarn install --immutable           # cold global cache: exit 0
head -1 node_modules/left-pad/index.js   # /* SOCKET-PATCHED */
head -1 node_modules/lp2/index.js        # upstream header: UNPATCHED
node -p "require('./node_modules/lp2/package.json').name"   # left-pad (version 1.3.0)

The copy's lock entry, which every reader skips:

"lp2@file:../../forks/left-pad-1.3.0.tgz::locator=b%40workspace%3Apackages%2Fb":
  version: 1.3.0
  resolution: "lp2@file:../../forks/left-pad-1.3.0.tgz#../../forks/left-pad-1.3.0.tgz::hash=5c8e4c&locator=b%40workspace%3Apackages%2Fb"

Vendored is the same with --mode vendored: it writes resolutions: {"left-pad": "file:./.socket/vendor/npm/<uuid>/left-pad-1.3.0.tgz"}, which yarn doesn't apply to the lp2 ident.

Expected vs actual

  • Expected: CLI_CONTRACT.md ("Contested locks", line 380): when another entry of the same lock resolves the wired name@version from a non-Socket source, "the package manager installs both entries, and that copy stays unpatched", so the reference is dropped with patched_ref_unattributable. The scans should name the unreached copy, as they already do for a same-name file: copy (redirect_yarn_berry_unsupported_protocol, vendor_override_conflict).
  • Actual: neither scan warns. Lock-only vex attests not_affected in both modes, and vendored vex still attests after a fresh --immutable install.

OS × version

Linux, Node 22, real yarn from @yarnpkg/cli-dist, nodeLinker: node-modules, cold global cache for each --immutable install, fresh copy of the tree. Every cell was run at least once; 4.18.1 file:-tgz/hosted was run twice.

yarn lp2 spec Mode Scan warns? Lock-only vex --immutable node_modules/lp2 vex after install
4.18.1 file: tgz hosted no not_affected ok unpatched declines (not_applied)
4.18.1 file: tgz vendored no not_affected ok unpatched not_affected
4.18.1 file: dir hosted no not_affected ok unpatched declines
4.18.1 file: dir vendored no not_affected ok unpatched not_affected
4.18.1 registry tarball URL hosted no not_affected ok unpatched declines
4.18.1 registry tarball URL vendored no not_affected ok unpatched not_affected
4.0.2 tgz / dir / URL hosted no not_affected ok unpatched declines
4.0.2 tgz / dir / URL vendored no not_affected ok unpatched not_affected
4.18.1 (control) "left-pad": "file:…" (same name) hosted / vendored refused loudly n/a n/a n/a n/a

I didn't probe macOS or Windows. This run couldn't use probe branches, and the behaviour lives in lock parsing, which isn't OS-specific. Yarn 2/3 are refused by both modes anyway.

First bad version: none bisected. Release 4.0.0 has no manifest-less lockfile VEX, so this isn't a regression of a shipped feature.

Suspect code

  • crates/socket-patch-core/src/vex/discover/yarn.rs:403: a file: / http locator that isn't Socket's returns silently (no resolved_elsewhere, nothing that contests the ref). Even the URL variant names left-pad/-/left-pad-1.3.0.tgz in the locator, and the file: tarball and directory are in the checkout, so a lock-only reader can tell that lp2 is left-pad@1.3.0.
  • crates/socket-patch-core/src/patch/redirect/mod.rs:~3841 (redirect_yarn_berry_unsupported_protocol / shared_descriptor) and crates/socket-patch-core/src/vendor/yarn_berry_lock.rs:1297 (vendor_override_conflict) only look at entries whose ident is the patched name, so the other-name copy isn't seen.
  • Vendored vex treats the mismatch as vendored_tree_out_of_sync and attests from the committed artifact. Its remedy ("re-run your package manager's install") can't help with a copy the resolutions pin never reaches.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions