Skip to content

With Bun's globalStore (Bun ≥ 1.3.14), agent mode patches and rolls back every other project sharing the store, and vex attests unpatched transitive copies as not_affected #635

Description

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

Summary

Bun 1.3.14 added a machine-wide store for the isolated linker: [install] globalStore = true in bunfig.toml, or BUN_INSTALL_GLOBAL_STORE=1. With it on, each node_modules/.bun/<name>@<version> entry is a symlink into <BUN_INSTALL_CACHE_DIR>/links/<name>@<version>-<hash>/, and every project on the machine links to the same directory. socket-patch doesn't recognize this store, and that has two effects:

  1. Agent mode writes into the shared store. scan --mode agent / apply follows node_modules/<dep> into <cache>/links/… and rewrites the file there. Every other project using that store is then patched, including ones that never ran socket-patch. rollback in one project silently unpatches all of them, including projects that recorded the patch in their own .socket/manifest.json. That project's vex then fails with not_applied. Fix agent apply writing into shared package stores (#332, #361) #486 (Agent mode writes the patch into PDM's shared install cache when PDM 2.0–2.12 installs packages as directory symlinks (install.cache + symlink) #332, Agent-mode apply and rollback on a pnpm project with enableGlobalVirtualStore patch (and unpatch) every other project that shares the store #361) added exactly this guard for pnpm's global virtual store and PDM's cache, but patch/shared_store.rs only knows those two layouts.
  2. Transitive packages are invisible. The .bun store walk keeps only real directories, so it skips every symlinked entry. Agent mode reports a transitive dependency as package_not_installed, leaves it unpatched, and still exits 0. Worse, hosted vex never looks at the installed copy: right after scan --mode hosted (before reinstalling), it attests an unpatched transitive is-number as verified / not_affected. With globalStore = false the same tree correctly gives not_applied. This is the 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 / 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 failure, brought back by the global store.

Impact

  • Patching one project silently changes the code other projects run, and rolling it back silently removes patches other projects depend on. Nothing in the envelope mentions it (status: success).
  • False VEX: a hosted project attests not_affected for a transitive package that is installed unpatched.
  • Agent mode silently skips every transitive dependency of a globalStore project.

Repro (Linux, real Bun, local patch-API mock)

The mock serves the public-proxy routes (/patch/batch, by-package, view with blob contents, blob, /patch/package, the tarball route). Each patch prepends /* SOCKET-PATCHED */ to index.js. SOCKET_PROXY_URL and SOCKET_PATCH_SERVER_URL point at it.

# usage: repro-gs.sh <bun> <dir> <bunfig|env>
B=$1; D=$2; CFG=$3
rm -rf "$D"; mkdir -p "$D/p1" "$D/p2"; export BUN_INSTALL_CACHE_DIR="$D/cache"
for p in p1 p2; do
  cd "$D/$p"
  [ $p = p1 ] && deps='"left-pad":"1.3.0","is-odd":"3.0.1"' || deps='"left-pad":"1.3.0"'
  printf '{"name":"%s","version":"1.0.0","dependencies":{%s}}' $p "$deps" > package.json
  if [ $CFG = bunfig ]; then printf '[install]\nlinker = "isolated"\nglobalStore = true\n' > bunfig.toml
  else printf '[install]\nlinker = "isolated"\n' > bunfig.toml; export BUN_INSTALL_GLOBAL_STORE=1; fi
  "$B" install
done
readlink "$D/p1/node_modules/.bun/left-pad@1.3.0"         # -> $D/cache/links/left-pad@1.3.0-6a490709ba3c5c8f
cd "$D/p2" && socket-patch scan --mode agent --json --yes # p2 records + applies left-pad
cd "$D/p1" && socket-patch scan --mode agent --json --yes # left-pad added; is-number skipped package_not_installed
node -e "const fs=require('fs');console.log(require.resolve('is-number',{paths:[fs.realpathSync(require.resolve('is-odd'))]}))"  # unpatched copy
cd "$D/p1" && socket-patch rollback --json --yes
head -1 "$D/p2/node_modules/left-pad/index.js"            # original bytes: p2's patch is gone
cd "$D/p2" && socket-patch vex --product pkg:npm/p2@1.0.0 --output v.json --json   # exit 1, not_applied

Output on main 045d7ec, Bun 1.4.2 (bunfig):

p2 agent scan: success [('pkg:npm/left-pad@1.3.0', 'added', None)]
  p2 left-pad: /* SOCKET-PATCHED */
p1 agent scan: success [('pkg:npm/left-pad@1.3.0', 'added', None), ('pkg:npm/is-number@6.0.0', 'skipped', 'package_not_installed')]
  p1 transitive is-number (loaded via is-odd): /*!            <- unpatched
p1 rollback: success
  p1 left-pad: /* This program is fre
  p2 left-pad: /* This program is fre   <- p2 never rolled back
p2 vex: error [('pkg:npm/left-pad@1.3.0', 'not_applied')]   exit=1

Hosted false attestation (one project, linker = "isolated", globalStore = true, deps left-pad@1.3.0 + is-odd@3.0.1):

bun install
socket-patch scan --mode hosted --json --yes     # bun.lock rewired, success, 2 redirected
socket-patch vex --product pkg:npm/p@1.0.0 --output v.json --json   # before reinstalling
globalStore=true : partialFailure [('pkg:npm/is-number@6.0.0', 'verified'), ('pkg:npm/left-pad@1.3.0', 'not_applied')]   <- is-number is still unpatched
globalStore=false: error          [('pkg:npm/is-number@6.0.0', 'not_applied'), ('pkg:npm/left-pad@1.3.0', 'not_applied')]

After a fresh frozen install, overwriting the store copy of the patched is-number with the original bytes still gives verified for it, so vex never reads those copies.

Expected vs actual

OS × version (Linux sandbox; 2+ runs each)

OS Bun config agent: cross-project apply/rollback agent: transitive skipped hosted vex on unpatched transitive
Linux 1.4.2 bunfig globalStore = true fail fail fail (verified)
Linux 1.4.2 BUN_INSTALL_GLOBAL_STORE=1 fail fail untested
Linux 1.3.14 bunfig / env fail / fail fail fail (verified)
Linux 1.3.13 bunfig globalStore = true (key not supported, so ignored) pass pass n/a
Linux 1.4.2 / 1.3.14 globalStore = false pass pass pass (not_applied)
macOS / Windows — — untested (probe branches currently blocked)

First bad Bun release: 1.3.14, the first build with globalStore / BUN_INSTALL_GLOBAL_STORE (absent from 1.3.13 and earlier). This isn't a socket-patch regression. It's a Bun layout socket-patch has never handled.

Suspect code

  • crates/socket-patch-core/src/patch/shared_store.rs:122 (shared_store_of_blocking) recognizes only <store>/v<N>/links (pnpm) and PDM's packages/<stem>/lib. Bun's <cache>/links/<name>@<ver>-<hash>/node_modules/<name> (with the cache's <name>@<ver>@@@1 / *.npm entries beside links) has no SharedStoreKind.
  • crates/socket-patch-core/src/crawlers/npm_crawler.rs:2143 (list_pnpm_shaped_store_entries_sync) filters .bun entries by file_type().is_dir(), which is false for a symlink. So every globalStore entry is dropped from the crawl, and vex/verify.rs never sees those copies.

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