Skip to content

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

Merged
Mikola Lysenko (mikolalysenko) merged 6 commits into
mainfrom
agent/fix-yarn-copy-invalid-identity
Oct 20, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 6 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 Yarn copy identity and VEX/hosted warning behavior on a security-sensitive path; scope is limited to manifest parsing with broad new tests, plus a CI uv pin.

Overview
Fixes #1598 by tightening how Yarn reads package.json on portal: / link: / file: copies. Empty or invalid npm name values and empty or non-semver version values no longer produce a fake identity; they behave like a missing key, so hosted scan names the copy as possibly UNPATCHED and VEX withholds attribution instead of reporting a clean not_affected.

Core: CopyRef::identity and shared manifest_name now use package_name / package_version (registry name + npm semver validation, build metadata stripped). manifest_name lives in copy.rs so linked, directory, and tarball copies share the same rules.

CI: Pipenv / pip legs pin uv 0.13.1 because 0.13.2 rejects Pipenv pylocks with environments = [].

Tests & docs: New hosted, on-disk, VEX discovery, and unit coverage; ecosystems.md documents the rule.

Reviewed by Cursor Bugbot for commit eb4de44. 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.

Stale Bugbot comment from a previous run.

@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

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Yarn Berry bug-hunt routine (ledger #305): correction to my earlier comment. You were right. I rebuilt both binaries with separate CARGO_TARGET_DIRs (they differ byte-for-byte; last run shared one target dir, so the "PR" binary was really main). With real yarn installs, this PR's head 022189d fixes every #1598 shape:

manifest in vend/lp main 657fce2 PR 022189d
{"version":"1.3.0"} (#1579 control), hosted 4.18.1 exit 1, redirect_copy_unpatched exit 1, redirect_copy_unpatched
"name": "", hosted 4.18.1 / 4.0.2 exit 0, VEX not_affected exit 1, redirect_copy_unpatched, vex_omitted
"name": "../x", hosted 4.18.1 exit 0, VEX not_affected exit 1, same as the control
"name": "left-pad", "version": "", hosted 4.18.1 / 4.0.2 exit 0, VEX not_affected exit 1, same as the control
"name": " left-pad" (link:), hosted 4.18.1 exit 0, VEX not_affected exit 1, same as the control
"name": "", vendored 4.18.1 exit 0, VEX not_affected exit 1, vendor_copy_unpatched + patched_ref_unattributable, no statement

Sorry for the noise.


Generated by Claude Code

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 20, 2026
uv 0.13.2 refuses the `environments = []` pylock that Pipenv 2026.8.0
writes, so the `e2e_vex_build pipenv::` leg fails in every merge-queue
group, this PR's included. This is the same change as #1639. It is a
no-op once main has it.

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

The merge queue dropped this PR because e2e (ubuntu-latest, e2e_vex_build, pipenv:: --ignored, 2026.8.0) failed. That failure doesn't come from this PR. The same test, pipenv::pipenv_pylock_projects_wire_what_pipenv_installs (uv pip install -r pylock.toml: "The target environment is not supported by the lockfile"), fails in every queue group today: #1599, #1609, #1612, #1614, #1617.

The cause is uv 0.13.2, which the unpinned setup-uv step picked up. #1639 explains it and fixes it. I merged main into this branch and ported #1639's pin (setup-uv version: "0.13.1" in ci.yml and pipenv-compatibility.yml) in eb4de44. That change becomes a no-op once #1639 lands. The yarn code is unchanged. On the merged tree, fmt and clippy are clean, the 361 yarn lib tests pass, and the 47 hosted_memory_engine tests pass. It needs re-queueing once CI is green.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Wenxin Jiang (@Wenxin-Jiang) this changed after your approval (on 022189d1), so it needs another look before it goes through the merge queue.

Besides merges from main, the only new commit is eb4de44: the CI-only uv pin from #1639, byte-for-byte the same 12 added lines (version: "0.13.1" on the two setup-uv steps in ci.yml and pipenv-compatibility.yml). No crate code changed. It's there because uv 0.13.2 fails the pipenv:: e2e leg on every merge-queue entry. Once #1639 lands it does nothing.

If that's fine with you, re-approving eb4de448 is all this needs.


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 eb4de44. Configure here.

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

3 participants