You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Hosted and vendored scans from a Bun workspace member with a stray bun.lock / bun.lockb pin that ignored lock, exit 0, and lock-only VEX attests not_affected while Bun installs the unpatched package #1101
[agent] Found by the scheduled Bun bug-hunt routine (ledger #306).
Summary
This is the Bun variant of #1094. The fix for #1094, PR #1095, deliberately leaves Bun out: its comment says a member holding "a lock its own manager reads (pnpm, yarn, Bun, vlt…)" keeps the own-lock shortcut, and the #1094 thread says the Bun variant was kept out of scope because it wasn't tested. Tested here with real Bun: Bun never reads a lock inside a workspace member.bun install at the root, and bun install run inside the member, both walk up to the workspace root and install the member from the root's bun.lock. A stray packages/a/bun.lock or bun.lockb, for example left behind when a package moved into a monorepo, is dead weight.
On main, the #884 / #901 member refusal (redirect_workspace_lockfile_elsewhere) is skipped whenever the member directory holds any npm-family lock (has_own_npm_family_lock, which includes bun.lock and bun.lockb). So a hosted scan or get <uuid> from such a member:
rewrites the member's ignored bun.lock / bun.lockb with the hosted pin,
reports status: success, redirected: 1, exit 0, with no warning,
leaves the root bun.lock untouched, so every bun install --frozen-lockfile (at the root or in the member) installs the unpatched package,
and socket-patch vex from the member then attests not_affected. That happens on a lockfile-only checkout, and on a hoisted install too, because the member has no node_modules of its own and vex falls back to the member lock.
scan --mode vendored does the same thing: it vendors into the ignored member lock, writes packages/a/.socket/, exits 0, and vex attests.
Impact
A signed-off VEX not_affected and a green CI step, while every install ships the vulnerable bytes. This is the same false-success shape as #884 and #1094, through a Bun lock.
Repro
A local mock of the patch API serves one free patch for left-pad@1.3.0, which prepends /* SOCKET-PATCHED */ to index.js. sp is socket-patch … --api-url <mock> --org o --api-token fake --patch-server-url <mock>.
mkdir -p stray ws/packages/a &&cd stray
echo'{"name":"a","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}'> package.json
bun install # stray/bun.lock (or bun.lockb with saveTextLockfile=false)cd ../ws
echo'{"name":"root","private":true,"workspaces":["packages/*"]}'> package.json
printf'[install]\nlinker = "hoisted"\n'> bunfig.toml # optional; isolated reproduces too (see the matrix)
cp ../stray/package.json packages/a/ && bun install # the root bun.lock governs the member
cp ../stray/bun.lock packages/a/ # the stray member lock Bun ignoresprintf'node_modules\n'> .gitignore && git init -q && git add -A && git commit -qm init
cd packages/a
sp scan --mode hosted --json --yes # exit 0, status success, redirect.redirected 1, rewrittenFiles ["bun.lock"] (the member's)
git -C ../.. status --short # M packages/a/bun.lock (root bun.lock untouched)
git clone -q ../.. /tmp/fresh &&cd /tmp/fresh && bun install --frozen-lockfile --cache-dir "$(mktemp -d)"
head -c 21 node_modules/left-pad/index.js # upstream bytes, no marker# the same from inside the member dir: cd packages/a && bun install --frozen-lockfile → upstream bytescd<ws>/packages/a && sp vex -O v.json # exit 0, one statement: not_affected (inline_mitigations_already_exist)
get <uuid> --mode hosted from the member gives the same result (success, rewrittenFiles: ["bun.lock"]).
Expected vs actual
Expected: CLI_CONTRACT (redirect_workspace_lockfile_elsewhere row) says a hosted run from a workspace member "whose lock lives in another directory" is refused, because "the rewriters, which read only the project directory, would pin nothing". For Bun the governing lock is always the workspace root's, whatever sits in the member, so the refusal should fire (or at least a loud warning that names the stray lock). Vendored should refuse the member (vendor_lockfile_missing) as it does without the stray file. vex must not attest a pin Bun never reads.
Actual: the refusal is skipped because the member has "a lock of its own". Exit 0, success, and a not_affected attestation, while Bun installs unpatched.
Controls: the same workspace from the root pins the root lock and a frozen install is patched (pass). macOS / Windows: not probed. The check is lock-path logic with no OS branch.
First bad version: none. Release 4.0.0 (npm @socketsecurity/socket-patch-linux-x64-gnu) also exits 0 and pins the member lock (Bun 1.4.2), so it isn't a regression.
Suspect code
crates/socket-patch-core/src/hosted/governing_root.rs:92: let workspace = if has_own_npm_family_lock(root) { None } else { … }. has_own_npm_family_lock (:247) counts bun.lock / bun.lockb from OWN_LOCKS (:58).
PR Fix npm workspace-member refusal skipped by a stray member lock (#1094) #1095 adds npm_member_stray_lock, but its other_own check (governing_root.rs:316 on the PR head) returns None whenever the member holds a Bun lock, so Bun keeps the shortcut. Bun's rule is the same as npm's: a member of a package.json workspace whose root holds bun.lock / bun.lockb is installed from the root lock. (Unlike yarn berry, Bun doesn't treat a nested lock as a separate project.)
The vendored lock inventory (crates/socket-patch-core/src/vendor/lock_inventory/view.rs) likewise treats the member lock as the project's own.
[agent] Found by the scheduled Bun bug-hunt routine (ledger #306).
Summary
This is the Bun variant of #1094. The fix for #1094, PR #1095, deliberately leaves Bun out: its comment says a member holding "a lock its own manager reads (pnpm, yarn, Bun, vlt…)" keeps the own-lock shortcut, and the #1094 thread says the Bun variant was kept out of scope because it wasn't tested. Tested here with real Bun: Bun never reads a lock inside a workspace member.
bun installat the root, andbun installrun inside the member, both walk up to the workspace root and install the member from the root'sbun.lock. A straypackages/a/bun.lockorbun.lockb, for example left behind when a package moved into a monorepo, is dead weight.On main, the #884 / #901 member refusal (
redirect_workspace_lockfile_elsewhere) is skipped whenever the member directory holds any npm-family lock (has_own_npm_family_lock, which includesbun.lockandbun.lockb). So a hostedscanorget <uuid>from such a member:bun.lock/bun.lockbwith the hosted pin,status: success,redirected: 1, exit 0, with no warning,bun.lockuntouched, so everybun install --frozen-lockfile(at the root or in the member) installs the unpatched package,socket-patch vexfrom the member then attestsnot_affected. That happens on a lockfile-only checkout, and on a hoisted install too, because the member has nonode_modulesof its own andvexfalls back to the member lock.scan --mode vendoreddoes the same thing: it vendors into the ignored member lock, writespackages/a/.socket/, exits 0, andvexattests.Impact
A signed-off VEX
not_affectedand a green CI step, while every install ships the vulnerable bytes. This is the same false-success shape as #884 and #1094, through a Bun lock.Repro
A local mock of the patch API serves one free patch for
left-pad@1.3.0, which prepends/* SOCKET-PATCHED */toindex.js.spissocket-patch … --api-url <mock> --org o --api-token fake --patch-server-url <mock>.get <uuid> --mode hostedfrom the member gives the same result (success,rewrittenFiles: ["bun.lock"]).Expected vs actual
redirect_workspace_lockfile_elsewhererow) says a hosted run from a workspace member "whose lock lives in another directory" is refused, because "the rewriters, which read only the project directory, would pin nothing". For Bun the governing lock is always the workspace root's, whatever sits in the member, so the refusal should fire (or at least a loud warning that names the stray lock). Vendored should refuse the member (vendor_lockfile_missing) as it does without the stray file.vexmust not attest a pin Bun never reads.not_affectedattestation, while Bun installs unpatched.Matrix (Linux, main
fe8455d)vexfrom memberbun.lockb.socket/writtennot_applied(membernode_moduleslinks the root store); lockfile-only checkout attestsbun.lockbcddf38d, hosted + vendoredControls: the same workspace from the root pins the root lock and a frozen install is patched (pass). macOS / Windows: not probed. The check is lock-path logic with no OS branch.
First bad version: none. Release 4.0.0 (npm
@socketsecurity/socket-patch-linux-x64-gnu) also exits 0 and pins the member lock (Bun 1.4.2), so it isn't a regression.Suspect code
crates/socket-patch-core/src/hosted/governing_root.rs:92:let workspace = if has_own_npm_family_lock(root) { None } else { … }.has_own_npm_family_lock(:247) countsbun.lock/bun.lockbfromOWN_LOCKS(:58).npm_member_stray_lock, but itsother_owncheck (governing_root.rs:316on the PR head) returnsNonewhenever the member holds a Bun lock, so Bun keeps the shortcut. Bun's rule is the same as npm's: a member of apackage.jsonworkspace whose root holdsbun.lock/bun.lockbis installed from the root lock. (Unlike yarn berry, Bun doesn't treat a nested lock as a separate project.)crates/socket-patch-core/src/vendor/lock_inventory/view.rs) likewise treats the member lock as the project's own.Related: #1094 / #1095 (npm), #884 / #901, #1097.
Probe runs: none (Linux sandbox only).