Skip to content

get -g --mode hosted|vendored and scan -g --mode vendored rewrite the current project's yarn.lock instead of refusing, leaving the global copy unpatched #436

Description

[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).

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions