Skip to content

Bun hosted and vendored modes skip a URL or file: tarball copy of the patched package without warning, and vendored vex attests not_affected (the #326 fix covers npm locks only) #497

Description

[agent] Found by the scheduled Bun bug-hunt routine (ledger #306).

Summary

A Bun project can depend on a package by remote tarball URL ("is-number": "https://registry.npmjs.org/is-number/-/is-number-6.0.0.tgz") or by file: tarball. If a registry copy of the same name@version is also in bun.lock (here, nested under a dependent), hosted and vendored scans rewire only the registry tuple.

  • The user's URL/file: tuple is left alone. That's correct in itself, since Bun installs it from its spec.
  • But there's no warning: status: success, redirect.warnings: [], no vendor warning event.
  • In vendored mode, vex (default and --no-verify) then attests not_affected for that name@version, even though the root copy the app loads (require('is-number')) is still the original bytes after a cold bun install --frozen-lockfile.
  • Hosted default vex correctly refuses (not_applied), but vex --no-verify also attests.

#326 / #345 fixed exactly this for package-lock.json: a redirect_npm_non_registry_entry_skipped / vendor_non_registry_entry_skipped warning, and vex attests nothing while a non-registry copy is in the lock. The Bun rewriters and the Bun VEX discovery never got the equivalent. It's the same class as #469 (bundled) and #471 (vlt bundled).

Impact

A false VEX attestation, plus a silent "success" that leaves the copy the application actually loads unpatched.

Repro (Bun 1.4.2, Linux)

cat > package.json <<'EOF'
{"name":"app","version":"1.0.0","dependencies":{
  "is-odd":"3.0.1",
  "is-number":"https://registry.npmjs.org/is-number/-/is-number-6.0.0.tgz"}}
EOF
bun install                      # is-odd@3.0.1 depends on is-number@^6 → nested registry copy
grep -E '"(is-number|is-odd/is-number)": \[' bun.lock
#   "is-number":        ["is-number@https://registry.npmjs.org/is-number/-/is-number-6.0.0.tgz", {}, "sha512-Wu1V…"]
#   "is-odd/is-number": ["is-number@6.0.0", "", {}, "sha512-Wu1V…"]
# a patch exists for pkg:npm/is-number@6.0.0 (and is-odd@3.0.1)
socket-patch scan --mode vendored --yes --json   # exit 0, status success, no warning
# fresh checkout (package.json, bun.lock, .socket/), empty cache:
bun install --frozen-lockfile                    # exit 0
node -e "console.log(require('fs').readFileSync(require.resolve('is-number'),'utf8').split('\n')[0])"
#   /*!          ← root copy, unpatched
# is-odd's nested is-number → /* SOCKET-PATCHED */
socket-patch vex --json --output vex.json
#   status success; pkg:npm/is-number@6.0.0 verified not_affected

The patch data came from a local mock of the patch API: batch, by-package, patches/package, view and the hosted tarball route. The patch prepends /* SOCKET-PATCHED */ to index.js.

Expected vs actual

Controls:

  • With only the URL dependency (no registry copy), vendored refuses with vendor_lock_entry_not_found and hosted warns redirect_bun_entry_not_found, both correctly.
  • Hosted default vex → not_applied for is-number, which is correct.

Matrix (Linux; main 61cfb9b; each cell run twice on 1.3.14 / 1.4.2)

Bun lock spec hosted scan warns vendored scan warns root copy patched after frozen install vendored vex hosted vex hosted vex --no-verify
1.1.45 text v0 URL no ✗ no ✗ no attests ✗ not_applied ✓ attests ✗
1.2.23 text v1 URL no ✗ no ✗ no attests ✗ not_applied ✓ attests ✗
1.3.14 text v1 URL no ✗ no ✗ no attests ✗ not_applied ✓ —
1.4.2 text v2 URL no ✗ no ✗ no attests ✗ not_applied ✓ attests ✗
1.4.2 text v2 file:./is-number-6.0.0.tgz — no ✗ no attests ✗ — —

Release 4.0.0 behaves the same on 1.4.2 (vendored attests, no warnings). It isn't a regression. macOS and Windows weren't probed, because this is lock-parsing logic, not OS-specific code.

Suspect code

  • crates/socket-patch-core/src/vex/discover/bun.rs:249: a non-Socket tuple only counts as resolved_elsewhere when it has a recorded or digit-leading version. A name@https://…/name-6.0.0.tgz or name@./x.tgz tuple has neither, so it never contests the vendored/hosted ref for the same name@version. Compare drop_non_registry_installs in vex/discover/npm.rs:202.
  • crates/socket-patch-core/src/patch/redirect/mod.rs:3773 (rewrite_bun_lock): warns only when no tuple matched (!matched_any). There's no stays-unpatched warning when a non-registry tuple of the same name@version is skipped beside a rewired one.
  • crates/socket-patch-core/src/vendor/bun_lock.rs:679 (preflight_package / classify): same, on the vendored side.

Activity

  1. added a commit that references this issue on Oct 1, 2026
  2. mikolalysenko commented on Oct 1, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged as priority:p1 (Bun). This is closely related to #469 / #471, which open PR #472 is fixing: that PR adds the bundled-copy contest to the Bun/vlt rewriters and to VEX discovery (vex/discover/bun.rs). This issue covers a different tuple shape, URL/file: non-registry tuples (the Bun counterpart of #326/#345). #472 doesn't cover it. It should be fixed at the same Bun contest boundary once #472 lands, ideally on top of it, to avoid conflicting edits. Not a duplicate.


    Generated by Claude Code

  3. mikolalysenko commented on Oct 2, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] New information: the binary bun.lockb path has the same defect (reproduced 2/2 on main 61cfb9b, Linux).

    Shape: Bun 1.1.45 bun install writes bun.lockb for {"is-number": "https://registry.npmjs.org/is-number/-/is-number-6.0.0.tgz", "is-odd": "3.0.1"}. The lock has a root URL copy of is-number@6.0.0 and a nested registry copy under is-odd.

    • scan --mode hosted: success, redirected: 2, warnings: [], skipped: []. Only the nested registry record is rewired.
    • scan --mode vendored: success, no warnings.
    • After a cold-cache bun install --frozen-lockfile on 1.1.45, 1.2.23 and 1.4.2 (all reading the same lockb), node_modules/is-odd/node_modules/is-number/index.js is patched, but the root node_modules/is-number/index.js that the app requires is not.
    • vex: vendored (default and --no-verify) and hosted --no-verify attest not_affected for pkg:npm/is-number@6.0.0. Hosted default vex correctly omits it (partialFailure).

    So the fix at the Bun contest boundary needs to cover the lockb codec's tuple walk as well as the text-lock one.


    Generated by Claude Code

  4. mikolalysenko commented on Oct 5, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] New information: still reproduces on main 6811b4e (Linux, Bun 1.4.2 text v2). In a lockfile-only checkout, the default hosted vex now gives a false not_affected too.

    Shape: {"left-pad": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", "dep": "file:./dep"}, where dep depends on left-pad@1.3.0 (a nested registry copy).

    • scan --mode hosted: success, redirected: 1, warnings: []. Only dep/left-pad is pinned.
    • A fresh bun install --frozen-lockfile leaves the root node_modules/left-pad unpatched. The nested copy is patched.
    • vex with node_modules present (default): correctly omits the purl (not_applied), as noted above.
    • vex in a checkout with only package.json, bun.lock and dep/ (the usual "attest in CI before install" flow): exit 0, not_affected for pkg:npm/left-pad@1.3.0, with and without --no-verify. Reproduced 2/2.

    There's no 4.0.0 baseline, because it has no manifest-less vex (Manifest not found). Lockfile-only discovery (lock_inventory/bun.rs, extended by #722) now also lists the URL tuple as left-pad@1.3.0, but VEX attests from the hosted pin without contesting the unwired URL copy.


    Generated by Claude Code

  5. mikolalysenko commented on Oct 7, 2026

    @mikolalysenko
    CollaboratorAuthor

    Claim-ID: agent/fix-bun-open-issues. Draft fix PR: #1009 (it covers all open pm:bun issues).

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