[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).
Summary
v5 refuses scan -g --mode hosted (and --global-prefix / SOCKET_GLOBAL=1) with exit 2: "global installs have no project lockfile to redirect". Three neighbouring combinations have no such guard. Each one silently runs the project workflow against whatever project the cwd is in:
| Command (run from inside a yarn classic project) |
Exit |
What happens |
get <uuid> -g --mode hosted |
0, status: success, redirect.redirected: 1 |
The project's yarn.lock is rewired to the hosted tarball. The global copy is untouched. |
get <uuid> --global-prefix <dir> --mode vendored (or -g) |
0 |
.socket/vendor/npm/<uuid>/ is written, and the project's yarn.lock is rewired to file:./.socket/vendor/…. The global copy is untouched. |
scan -g --mode vendored |
1 (partial_failure) |
Discovery runs over the global trees (npm + yarn global, 271 packages here). Every global package that also happens to be in the project lock is then vendored into the project yarn.lock. The rest fail with yarn.lock has no rewritable block for …. |
Run outside any project, get -g --mode hosted still exits 0 success. It reports redirected: 0 and an npm-only redirect_npm_no_lockfile warning, and nothing is patched.
Impact
- A user who asks for a global patch gets an unrelated project's lockfile changed, with no warning. That can mean the repo they happen to
cd into, CI's checkout, or $HOME with a stray yarn.lock. Meanwhile the global tool they meant to patch stays vulnerable, and get reports success (exit 0).
scan -g --mode vendored mixes the two scopes: global discovery decides what gets vendored into the project. The maintainer checklist for global mode (ledger Bug hunt ledger: Yarn classic (1.x) #304) says -g must touch only the global location.
- The behaviour is the same on yarn 1.0.2 / 1.10.1 / 1.22.22 and on Linux, macOS and Windows (table below). It isn't specific to a yarn version. The same code path applies to any project lockfile; I've only verified yarn.lock.
Repro
Mock patch API on 127.0.0.1:8765 (patch 11111111-… for pkg:npm/[email protected]); API="--api-url http://127.0.0.1:8765 --org org --api-token fake --patch-server-url http://127.0.0.1:8765".
yarn global add [email protected] # the global copy we want patched
G="$(yarn global dir)/node_modules"
mkdir proj && cd proj
echo '{"name":"proj","version":"1.0.0","private":true,"dependencies":{"left-pad":"1.3.0"}}' > package.json
yarn install && cp yarn.lock yarn.lock.orig
socket-patch get 11111111-1111-4111-8111-111111111111 -g --mode hosted $API --json
# status "success", redirect.redirected 1, rewrittenFiles ["yarn.lock"]; exit 0
diff yarn.lock.orig yarn.lock # resolved -> http://127.0.0.1:8765/patch/npm/left-pad/1.3.0/… (project rewired)
head -1 "$G/left-pad/index.js" # unpatched upstream bytes
cp yarn.lock.orig yarn.lock
socket-patch get 11111111-1111-4111-8111-111111111111 --global-prefix "$G" --mode vendored $API --json # exit 0
grep resolved yarn.lock # file:./.socket/vendor/npm/11111111-…/left-pad-1.3.0.tgz#…
cp yarn.lock.orig yarn.lock; rm -rf .socket
socket-patch scan -g --mode vendored $API --json # exit 1; project yarn.lock rewired again
socket-patch scan -g --mode hosted $API # control: exit 2, "--global cannot be used with --mode hosted"
Expected vs actual
- Expected: CLI_CONTRACT.md ("Mode resolution") says that with
--global/--global-prefix there is "no project lockfile to rewire", and an explicit --mode hosted there is "a usage error (exit 2: global installs have no project lockfile to redirect)". The exit-code table lists that conflict under 2. The same reasoning covers get, which shares scan's mode enum and says global targeting means agent mode. It also covers vendored mode, which likewise only rewires project lockfiles. All of these should refuse before writing anything, or at the very least never write outside the global tree.
- Actual: only
scan --mode hosted is guarded. get honours an explicit --mode hosted|vendored alongside -g, and scan honours --mode vendored alongside -g, so the project in the cwd is rewired and the global copy is left unpatched.
OS × yarn matrix (main 2463257)
| OS |
yarn 1.0.2 |
yarn 1.10.1 |
yarn 1.22.22 |
| Linux (sandbox + ubuntu-latest) |
repro |
repro |
repro |
| macOS (macos-latest) |
repro |
repro |
repro |
| Windows (windows-latest) |
repro |
repro |
repro |
Every cell: get -g --mode hosted exit 0 with the project lock rewritten, and get -g --mode vendored exit 0 with the project lock rewritten. Control: scan -g --mode hosted exits 2 everywhere.
Not a v5 regression: the v4.0.0 release does the same for all three, and on v4.0.0 scan -g --mode hosted also exited 0. v5 added the scan/hosted guard only.
Suspect code
crates/socket-patch-cli/src/commands/get.rs:2523: args.mode.unwrap_or(if … is_global() { Agent } else { Hosted }) applies the global→agent default only when --mode is absent. Nothing rejects an explicit Hosted/Vendored together with is_global().
- The scan guard (in
resolve_mode_flags, per the contract) covers Hosted only, not Vendored.
Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36827174726 (3 OS × yarn 1.0.2 / 1.10.1 / 1.22.22, cells get_g_hosted / get_g_vendored).
[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).
Summary
v5 refuses
scan -g --mode hosted(and--global-prefix/SOCKET_GLOBAL=1) with exit 2: "global installs have no project lockfile to redirect". Three neighbouring combinations have no such guard. Each one silently runs the project workflow against whatever project the cwd is in:get <uuid> -g --mode hostedstatus: success,redirect.redirected: 1yarn.lockis rewired to the hosted tarball. The global copy is untouched.get <uuid> --global-prefix <dir> --mode vendored(or-g).socket/vendor/npm/<uuid>/is written, and the project'syarn.lockis rewired tofile:./.socket/vendor/…. The global copy is untouched.scan -g --mode vendoredpartial_failure)yarn.lock. The rest fail withyarn.lock has no rewritable block for ….Run outside any project,
get -g --mode hostedstill exits 0success. It reportsredirected: 0and an npm-onlyredirect_npm_no_lockfilewarning, and nothing is patched.Impact
cdinto, CI's checkout, or$HOMEwith a strayyarn.lock. Meanwhile the global tool they meant to patch stays vulnerable, andgetreports success (exit 0).scan -g --mode vendoredmixes the two scopes: global discovery decides what gets vendored into the project. The maintainer checklist for global mode (ledger Bug hunt ledger: Yarn classic (1.x) #304) says-gmust touch only the global location.Repro
Mock patch API on
127.0.0.1:8765(patch11111111-…forpkg:npm/[email protected]);API="--api-url http://127.0.0.1:8765 --org org --api-token fake --patch-server-url http://127.0.0.1:8765".Expected vs actual
--global/--global-prefixthere is "no project lockfile to rewire", and an explicit--mode hostedthere is "a usage error (exit 2: global installs have no project lockfile to redirect)". The exit-code table lists that conflict under2. The same reasoning coversget, which shares scan's mode enum and says global targeting means agent mode. It also covers vendored mode, which likewise only rewires project lockfiles. All of these should refuse before writing anything, or at the very least never write outside the global tree.scan --mode hostedis guarded.gethonours an explicit--mode hosted|vendoredalongside-g, andscanhonours--mode vendoredalongside-g, so the project in the cwd is rewired and the global copy is left unpatched.OS × yarn matrix (main
2463257)Every cell:
get -g --mode hostedexit 0 with the project lock rewritten, andget -g --mode vendoredexit 0 with the project lock rewritten. Control:scan -g --mode hostedexits 2 everywhere.Not a v5 regression: the v4.0.0 release does the same for all three, and on v4.0.0
scan -g --mode hostedalso exited 0. v5 added the scan/hosted guard only.Suspect code
crates/socket-patch-cli/src/commands/get.rs:2523:args.mode.unwrap_or(if … is_global() { Agent } else { Hosted })applies the global→agent default only when--modeis absent. Nothing rejects an explicitHosted/Vendoredtogether withis_global().resolve_mode_flags, per the contract) coversHostedonly, notVendored.Probe run: https://github.com/SocketDev/socket-patch/actions/runs/36827174726 (3 OS × yarn 1.0.2 / 1.10.1 / 1.22.22, cells
get_g_hosted/get_g_vendored).