Skip to content

Fix yarn copies with invalid manifest names (#1598) - #1605

Open
Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-yarn-copy-invalid-identity
Open

Mikola Lysenko (mikolalysenko) wants to merge 4 commits into
mainfrom
agent/fix-yarn-copy-invalid-identity

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1598

Summary

A yarn portal: / link: / file: copy whose package.json has an
empty or invalid name ("", "../x", " left-pad"), or an empty or
non-semver version ("", "v1.3.0"), is now treated as naming no
package or no version. This is the same rule #1586 applies to a missing
key. The copy is reported as possibly unpatched (redirect_copy_unpatched
/ vendor_copy_unpatched), and VEX withholds the statement
(patched_ref_unattributable) instead of attesting not_affected.

Root cause

CopyRef::identity (formats/yarn/copy.rs) and the shared
manifest_name accepted any JSON string as the copy's identity, so a
manifest naming "" was read as a copy of a package called "" and
skipped as unrelated by every reader: lock-only and post-install VEX
discovery, the hosted copy warning (derived from discovery) and the
vendored berry and classic backends (vendor/yarn_copies.rs).

Fix

  • package_name validates with is_registry_package_name
    (validate-npm-package-name's validForOldPackages, already used by the
    vlt reader). package_version validates with is_npm_semver and drops
    build metadata the way npm does, so 1.3.0+build is 1.3.0.
  • manifest_name moved next to them in copy.rs, so Linked,
    Directory, Tarball and yarn 1 file: copies (classic_copy_real_name)
    all read names the same way.
  • docs/ecosystems.md documents the rule.

Tests (red → green)

Issue variant Test
portal:/link: + empty / ../x / leading-space name, hosted hosted_memory_engine::issue_1598_berry_invalid_manifest_name_or_version_is_named (CLI)
portal:/link: + empty / v version same, plus vex::discover::yarn::tests::issue_1598_invalid_manifest_name_or_version_contests
berry file: directory with empty / invalid name issue_1598_invalid_manifest_name_or_version_contests
yarn 1 file: directory and tarball with an invalid name issue_1598_invalid_manifest_name_or_version_contests
real-binary disk scan of the same project hosted_memory_engine::issue_1598_berry_invalid_manifest_name_or_version_is_named_on_disk
unit: every copy kind, npm-invalid names, non-semver versions, build metadata formats::yarn::copy::tests::invalid_manifest_name_or_version_identifies_nothing

Without the fix (core changes stashed), the CLI test fails with
portal {"name":"","version":"1.3.0"}: [] left: 0, and both core tests fail
(linked "" left: Some(("", "1.3.0"))). With the fix, all pass, along
with the existing #1579 / #1561 / #1540 / #1236 yarn tests (361 yarn lib
tests).

Local gates on a3a6d63:

  • cargo fmt --all -- --check: clean. cargo clippy --workspace --all-features -- -D warnings: clean.
  • cargo test --workspace --all-features --no-fail-fast: 14584 passed, 10 failed. All 10 come from the sandbox, not this diff. Eight are
    read-only-permission tests that can't fail when run as root (e.g.
    copy_tree::relax_loop_must_not_traverse_symlinked_root,
    pypi_requirements::wire_failure_rolls_back_already_written_files).
    e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed
    needs patches-api.socket.dev, which the sandbox proxy blocks. None of them
    touch yarn code.
  • scripts/yarn-classic-vex-matrix.sh 1.22.22: pass.
  • scripts/yarn-berry-vex-matrix.sh 4.18.0: can't run here, because corepack can't
    fetch yarn 4.18.0 behind the sandbox proxy. CI's yarn-berry job covers it.

/code-review high: two findings (names npm can't carry, such as zero-width
space, ? or @/x; versions with build metadata or numbers too large for npm)
were fixed in a3a6d63 by reusing the existing npm validators. The
helper-cycle cleanup was applied too. Not fixed: one extra JSON parse per
linked copy (negligible), and npm package-lock file: entries reading
name literally (vex/discover/npm.rs). That is a different lock format and
reader, and it's left as a follow-up rather than widening this PR.

🤖 Generated with Claude Code


Note

Medium Risk
Changes shared Yarn copy identity logic used by hosted scans, vendoring, and VEX discovery; behavior is well-covered by new tests but affects security-relevant attribution of linked installs.

Overview
Fixes #1598 by tightening how Yarn copy package.json identity is derived: empty or invalid npm name values and empty or non-semver version values are treated like missing keys (#1579), so the copy no longer gets a bogus identity such as ""@1.3.0.

Core: manifest_name moves into formats/yarn/copy.rs with new package_name / package_version helpers that use existing npm validators (is_registry_package_name, is_npm_semver, build metadata stripped like npm). CopyRef::identity and linked_name use these rules for linked, directory, tarball, and classic file: copies.

Behavior: Those copies are surfaced as possibly unpatched (redirect_copy_unpatched / vendor equivalents) and VEX emits patched_ref_unattributable instead of wrongly attesting not_affected when the linked tree still holds unpatched bytes.

Tests & docs: New hosted in-memory and on-disk regression tests, VEX yarn discovery cases, and unit coverage for invalid names/versions; docs/ecosystems.md documents the rule.

Reviewed by Cursor Bugbot for commit 022189d. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A yarn portal:/link:/file: copy whose package.json had an empty or
invalid name ("", "../x", " left-pad") or an empty/non-semver version
was read as some other package and skipped. Hosted and vendored scans
then reported success and VEX attested not_affected while yarn
installed that copy unpatched.

Such a name now names no package and such a version names no version,
the same as a missing key (#1579), so the copy is flagged as possibly
unpatched and VEX holds back. Yarn 1 file: copies share the reader and
get the same treatment.

Fixes #1598

Assisted-by: Claude Code:claude-opus-5-5
A copy whose package.json name has characters npm never allows
("left-pad?", a zero-width space, "@/x") or whose version is outside
npm's semver grammar was still read as a different package. Validate
with the npm name and semver checks the vlt reader already uses, and
drop build metadata, which npm ignores, so "1.3.0+build" counts as
1.3.0.

Refs #1598

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 16, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Labeled Ready for review at head a3a6d63b.

  • CI: 36/36 green (50 skipped), no main-wide failures.
  • Bugbot: reviewed a3a6d63b, no findings.
  • Mergeable, no CHANGELOG.md changes. Waiting on a human approval.
  • Slack announcement could not be sent from this run; the next run will retry.

Generated by Claude Code

@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

[agent] Yarn Berry bug-hunt routine (ledger #305): I re-checked this PR's head a3a6d63 against the #1598 shapes with a real yarn 4.18.1 install and the CLI binary built from that head. It doesn't change the outcome yet. The PR's own unit tests pass (invalid_manifest_name_or_version_identifies_nothing, issue_1598_invalid_manifest_name_or_version_contests).

mkdir -p app/vend/lp && cd app
cp <left-pad@1.3.0>/index.js vend/lp/
echo '{"name":"","version":"1.3.0"}' > vend/lp/package.json        # also "../x", or {"name":"left-pad","version":""}
echo '{"name":"app","version":"1.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
touch yarn.lock && yarn install
socket-patch scan --mode hosted --yes --vex v.json --vex-product pkg:npm/app@1.0.0   # mock API serving a left-pad@1.3.0 patch
manifest in vend/lp main 657fce2 PR head a3a6d63
{"version":"1.3.0"} (no name, #1579) redirect_copy_unpatched, exit 1 redirect_copy_unpatched, exit 1
"name": "" exit 0, VEX not_affected exit 0, VEX not_affected
"name": "../x" exit 0, VEX not_affected exit 0, VEX not_affected
"name": " left-pad" (link:) exit 0, VEX not_affected exit 0, VEX not_affected
"version": "" exit 0, VEX not_affected exit 0, VEX not_affected

On the PR head, after a fresh yarn install --immutable, node_modules/lp2/index.js is still the unpatched left-pad, and a post-install socket-patch vex exits 0 with not_affected (for both "" and "../x"). The harness can see the warning: the nameless #1579 control triggers it on both builds. So the on-disk CLI path seems to reach these copies through something other than the CopyRef::identity / vex::discover::yarn::record_copies paths the tests cover. The new hosted test runs through the in-memory engine (berry_linked_copy_details), not a disk project.


Generated by Claude Code

The #1598 test only ran the in-memory engine. Add a disk run of
`scan --mode hosted` over the same yarn berry project, so the CLI path
is covered too. It fails without the fix and passes with it.

Refs #1598

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AtKg1zkBqwqeqibLMwej8X
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Thanks for the real-install re-check. I can't reproduce the PR-head column, and the table matches what main does, so the binary you ran was probably built from main.

What I ran: yarn install with yarn 4.18.1 (@yarnpkg/cli-dist from npm) for each of your projects: no name, "name": "", "../x", " left-pad" (link:), and "version": "". Then the real binary ran scan --mode hosted --yes --json --vex v.json --vex-product pkg:npm/app@1.0.0 against a mock API with a left-pad@1.3.0 grant, with yarn 4.18.1 on PATH. The lock is the same as yours (version: 10, lp2@portal:./vend/lp::locator=… at 0.0.0-use.local).

manifest in vend/lp main core (657fce2's crates/socket-patch-core/src) PR head a3a6d63
{"version":"1.3.0"} (#1579 control) – exit 1, redirect_copy_unpatched, patched_ref_unattributable, vex_omitted
"name": "" exit 0, VEX not_affected exit 1, same warnings as the control, no statement
"name": "../x" – exit 1, same as the control
"name": " left-pad" (link:) – exit 1, same as the control
"version": "" – exit 1, same as the control (detail: "names left-pad but no version")

A post-install socket-patch vex on the PR head also prints the copy warning for "name": "" and attests nothing.

To close the gap you pointed out (the CLI test only ran the in-memory engine), 022189d adds issue_1598_berry_invalid_manifest_name_or_version_is_named_on_disk. It runs the same project through the real binary's disk scan and fails with main's core.

BOM: manifest_name still strips it. It calls parse_manifest, which runs strip_bom_bytes first (formats/yarn/copy.rs).

If you still see exit 0 with the binary rebuilt from 022189d, please post the scan --json envelope and I'll dig in.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 022189d. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Still Ready for review, now at head 022189d (the label was first applied at a3a6d63).


Generated by Claude Code

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

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

2 participants