Skip to content

With Bun's isolated linker, vex attests a hosted patch as not_affected (verified) while the installed copy under node_modules/.bun is still unpatched (v5 regression) #405

Description

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

Summary

With Bun's isolated linker, socket-patch vex attests a hosted-mode patch as not_affected and reports it verified, even though the copy Bun actually installed (under node_modules/.bun/<name>@<version>/node_modules/<name>) does not carry the patch. The isolated linker is opt-in on Bun 1.2.x (linker = "isolated") and the default for fresh workspaces on Bun 1.3.x and 1.4.x.

v5 hosted mode is the default for scan. In that mode, vex treats a purl with no consumed copy as "nothing installed" and attests it from the lockfile pin. The npm copy lookup doesn't look inside Bun's .bun/ store, so a transitive package there is never found. That makes the pin the only evidence even when a stale (pre-reinstall) or tampered copy is installed. A hoisted install of the same project is handled correctly: not_applied, omitted from the document.

This is a regression from 4.0.0. 4.0.0 omits the same purl from the document (package_not_found).

Related: #366 shares the root cause (agent mode can't see packages under node_modules/.bun). That issue is about agent-mode scan/apply. This one is about hosted-mode VEX making a false attestation. #373 is the Deno counterpart of #366.

Impact

The document says not_affected / "Patched via Socket patch (redirected)" for a dependency the running code loads unpatched. That happens in two cases, both with exit 0 and status: success:

  1. Stale tree: right after scan rewrote bun.lock and before the user re-installs. The installed copy still has the vulnerable bytes, and vex already attests.
  2. Tampered or wrong install: after a real fresh bun install --frozen-lockfile, the hosted copy lives at node_modules/.bun/is-number@http+++…/node_modules/is-number. Removing the patch from that file doesn't change the verdict: it's still verified. With the hoisted linker the same edit gives not_applied.

Every Bun ≥ 1.3 workspace that runs socket-patch scan && socket-patch vex hits case 1, because fresh workspaces default to the isolated linker.

Repro (Linux, Bun 1.4.2, main 2463257)

The patch API is a local mock that serves the batch, patches/package, patches/view and hosted tarball routes. SOCKET_PATCH_SERVER_URL points at it, so its tarball URL counts as a hosted pin. The patch prepends /* SOCKET-PATCHED … */ to is-number@7.0.0/index.js.

mkdir app && cd app
echo '{"name":"app","version":"1.0.0","dependencies":{"to-regex-range":"5.0.1"}}' > package.json
printf '[install]\nlinker = "isolated"\n' > bunfig.toml      # omit on a Bun >=1.3 workspace: it's the default there
bun install
# node_modules/.bun/is-number@7.0.0/node_modules/is-number   <- transitive, only in the store

socket-patch scan --mode hosted --json --yes --api-url $MOCK --org org --api-token fake
#  status success, redirect.redirected 1 (bun.lock now pins the hosted URL)
grep -c SOCKET-PATCHED node_modules/.bun/is-number@7.0.0/node_modules/is-number/index.js   # 0 (not reinstalled yet)

socket-patch vex --json --output out.vex.json --api-url $MOCK --org org --api-token fake
#  status "success", events: [{"action":"verified","purl":"pkg:npm/is-number@7.0.0", ... "status":"not_affected"}]
#  out.vex.json: not_affected, "Patched via Socket patch 2222…(redirected)"

Control in the same state: linker = "hoisted" gives is-number at node_modules/is-number and vex → skipped not_applied, no_applicable_patches, no document.

Workspace variant, with no bunfig on Bun 1.3.14 or 1.4.2: the root has workspaces: ["packages/*"], and packages/a depends on to-regex-range@5.0.1 and left-pad@1.3.0. After a hosted scan and before a reinstall, vex reports left-pad as not_applied, because the direct dep is reached through its node_modules/left-pad symlink. It reports is-number as verified / not_affected, although neither store copy is patched.

Tamper variant (measured on Linux, Bun 1.4.2): run a fresh checkout and bun install --frozen-lockfile, so the hosted copy is installed and patched. Then delete the marker line from node_modules/.bun/is-number@http+++…/node_modules/is-number/index.js. vex still reports verified. The hoisted control reports not_applied.

Expected vs actual

  • Expected (CLI_CONTRACT.md, "Patched via Socket patch (redirected)" row and "Manifest-less VEX"): "a post-install socket-patch vex re-proves the lockfile wiring and hash-verifies the installed copy the build consumes". Only "with nothing installed" may it attest a discovered reference from its pin. HostedCopies (crates/socket-patch-core/src/vex/verify.rs:83-105) says a shared-location ecosystem's pristine copy "IS what runs" and must fail verification.
  • Actual: the consumed copy under node_modules/.bun/ is invisible to the lookup, so vex takes the "nothing installed" branch and attests from the pin, with action: verified.

OS × version (hosted scan → vex before reinstall)

OS Bun 1.2.23 (linker = "isolated") Bun 1.3.14 (isolated / default workspace) Bun 1.4.2 (isolated / default workspace) hoisted control
Linux fail fail / fail fail / fail pass (not_applied)
macOS (macos-latest) fail fail / fail fail / fail pass
Windows (windows-latest) fail fail / fail fail / fail pass

On Bun 1.2.23 a default workspace still installs hoisted, so that cell is correctly not_applied on all 3 OSes. That's expected, not a fix.

Regression check (Linux, Bun 1.4.2, isolated): release 4.0.0 → omitted (package_not_found), pass. main 2463257 (#277, v5) → attested, fail. So the first bad commit is the v5 consolidation (#277), the one that added manifest-less VEX's "attest from the pin when nothing is installed".

Suspect code

  • crates/socket-patch-cli/src/commands/vex_consumed.rs:69-121 (hosted_consumed_copies): npm's shared-location copies come from the crawler's installed map plus the alias walk and identity fallback. None of these walks node_modules/.bun/*/node_modules/<name>. with_store_variants (:323) only expands copies that were already found.
  • crates/socket-patch-core/src/vex/verify.rs:98-105: an empty HostedCopies.paths means "none installed", which the lockfile basis then excuses. The npm crawler's missing .bun store discovery (the same gap as Bun isolated linker: transitive packages under node_modules/.bun are "not installed" in agent mode, and scan --mode agent exits 0 with them unpatched #366, crates/socket-patch-core/src/crawlers/npm_crawler.rs) turns that into a false attestation.

A fix for #366 that teaches the crawler the .bun store would probably fix this too. Even so, vex should probably refuse to take the "nothing installed" branch while node_modules/.bun/ exists.

Probe run (3 OS × Bun 1.2.23 / 1.3.14 / 1.4.2, main built on each runner; cases iso-bunfig, ws-default, hoisted-control): https://github.com/SocketDev/socket-patch/actions/runs/36801259018

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions