Skip to content

fix(vendor): match bun tuples on version, keep CRLF - #180

Open
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/bun-vendor-version-classify
Open

fix(vendor): match bun tuples on version, keep CRLF#180
Mikola Lysenko (mikolalysenko) wants to merge 1 commit into
mainfrom
fix/bun-vendor-version-classify

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

Two findings from the 2026-08-13 vendored-bun audit (origin/main 4e5288e):

  1. classify() Ours arm ignores version — vendoring two versions of one package cross-clobbers bun.lock entries (high). The already-vendored 3-tuple classification matched on package name alone, never the vendored leaf's version. With patches for two versions of one package in the same bun.lock (e.g. root left-pad@1.2.0 plus nested haspad/left-pad@1.3.0), the second vendor pass classified the first pass's fresh tuple as its own stale edit and rewrote it to the OTHER version's tarball. Both patches then reported applied with zero warnings while bun install --frozen-lockfile silently deduped everything to one version — the other patch was not in effect despite state.json claiming it was. A later vendor --revert restored neither entry (the clobbering record's original was None), deleted both uuid dirs, and left the lock pointing at a deleted tarball: frozen installs hard-fail with ENOENT. The same name-only match let the pre-flight has_match gate accept a lock whose only same-name entries were vendored tuples of another version.

  2. CRLF bun.lock: rewritten entry lines silently drop the \r (low). vendor_bun splits on \n, the grammar tolerantly trims a trailing \r while parsing, but the rebuilt entry line re-emitted LF only — leaving one mixed-ending line in an otherwise-CRLF file (noisy diffs and autocrlf churn on Windows checkouts), in a module whose contract is byte-exact conservative surgery.

Fix

  • classify()'s Ours arm now also requires the parsed vendor path's leaf to equal the target's uuid-independent tgz_rel_leaf(name, version) (<name>-<version>.tgz, scope-aware — the exact leaf stage_patch_pack produces), so vendored tuples of another version of the same package classify as foreign and are never touched. This is the same single classification both the pre-flight gate and the rewrite loop consume, so both holes close together. Same-version re-vendoring under a new patch uuid still rewrites as before.
  • Rewritten entry lines re-emit a trailing \r detected on the original line, so a CRLF lock stays uniformly CRLF through vendor, in-sync re-run, and revert.

No skipped findings.

Testing

  • Three regression tests, each independently verified red on pre-fix logic (both fix conditions temporarily neutered: exactly the 3 new tests fail) and green after:
    • two_vendored_versions_of_one_package_do_not_cross_clobber — BN4C two-version vendor: each instance points at its own uuid/version tarball, the second pass records the true registry original, and a full revert byte-restores the lock with both uuid dirs removed.
    • preflight_refuses_when_only_other_version_vendored_tuples_exist — lock whose only same-name entry is a vendored other-version tuple refuses vendor_lock_entry_not_found and never rewrites.
    • crlf_lock_keeps_crlf_on_rewritten_lines_and_reverts_byte_exact — CRLF lock stays CRLF through vendor, byte-stable in-sync re-run, and byte-exact revert.
  • cargo test -p socket-patch-core --lib: 2078 passed, 0 failed.
  • cargo test -p socket-patch-cli --test e2e_vendor_bun_build: 6 passed, 0 failed.
  • cargo test -p socket-patch-cli --test e2e_redirect_bun_build: 7 passed, 0 failed (hosted bun rewriter untouched — it uses separate classification).

🤖 Generated with Claude Code


Note

Medium Risk
Changes lockfile classification and line re-emission in the bun vendor path; incorrect matching could still break frozen installs or revert, but the change narrows matching logic to fix a known cross-clobber bug with targeted tests.

Overview
Fixes bun.lock vendoring so two patched versions of the same package can coexist without one vendor pass rewriting the other’s tuple, and so CRLF locks stay byte-consistent through edit and revert.

Version-aware classification: classify()’s vendored (Ours) arm now requires the vendor tarball leaf to match tgz_rel_leaf(name, version) (same <name>-<version>.tgz shape as staging), not just the package name. That applies to both the pre-flight “has a rewritable entry” check and the rewrite loop, so another version’s vendored tuple is treated as foreign—refused when there’s no registry match, never cross-clobbered when vendoring a second version.

CRLF: Rewritten package entry lines re-emit a trailing \r when the original line had one, so mixed LF/CRLF doesn’t appear after vendor or revert.

Three regression tests cover dual-version vendoring/revert, pre-flight refusal when only other-version vendored tuples exist, and CRLF round-trip.

Reviewed by Cursor Bugbot for commit 02e4c37. Configure here.

The bun backend's already-vendored classification matched on package
name alone, never the vendored leaf's version. With patches for two
versions of one package in the same bun.lock (a root instance plus a
nested one), the second vendor pass classified the first pass's fresh
tuple as its own stale edit and rewrote it to the OTHER version's
tarball: bun then silently installed the wrong version everywhere,
state.json claimed both patches applied, and a later revert left the
entry pointing at a deleted tarball (frozen installs hard-fail with
ENOENT). The same name-only match let the pre-flight accept a lock
whose only same-name entries were vendored tuples of another version.

The Ours arm now also requires the parsed vendor path's leaf to equal
the target's uuid-independent `<name>-<version>.tgz` leaf, so
other-version vendored tuples classify as foreign and are never
touched; re-vendoring the same version under a new patch uuid still
rewrites as before.

Also preserve a trailing `\r` on rewritten entry lines: the grammar
tolerantly trimmed it while parsing a CRLF bun.lock but the rebuilt
line re-emitted LF only, leaving one mixed-ending line in an otherwise
CRLF file (noisy diffs and autocrlf churn on Windows checkouts).

Regression tests pin both: a two-version vendor + revert round-trip, a
pre-flight refusal when only another version's vendored tuples exist,
and a CRLF lock that stays CRLF through vendor, re-run, and revert.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants