Skip to content

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

Open

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_affected

Swap 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 warns redirect_copy_unpatched, vendored warns vendor_copy_unpatched, and VEX holds back, exactly as for a nameless manifest. CLI_CONTRACT's VEX rule is that not_affected is attested only for a patch that is actually applied to what gets installed.
  • Actual: exit 0 success, and every VEX path attests not_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): returns Some((name, version)) for any string name/version, including "" or a name that fails is_safe_npm_name. linked_name (:101) filters invalid names, but it's consulted only when identity returns None.
  • crates/socket-patch-core/src/formats/yarn/source.rs:131 (manifest_name): returns Some(""), so Directory/Tarball copies with an empty name are skipped too (vendor/yarn_copies.rs:116-118 Some(_) => continue; the same decision in vex/discover/yarn.rs:893).
  • manifest_name is shared with yarn classic copy detection, so classic file: copies may have the same gap (not tested here).

Activity

  1. mikolalysenko commented on Oct 16, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged as p1 (yarn berry, false not_affected). Confirmed on main 657fce2: CopyRef::identity (formats/yarn/copy.rs, Linked arm) returns any string name/version unvalidated, and manifest_name (formats/yarn/source.rs) returns Some(""), so only linked_name applies is_safe_npm_name. No open PR covers it.


    Generated by Claude Code

  2. mikolalysenko commented on Oct 16, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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

  3. mikolalysenko commented on Oct 16, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1605


    Generated by Claude Code

  4. added 3 commits that reference this issue on Oct 16, 2026
    cd59fac
    a3a6d63
    022189d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions