[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
pnpm keeps one pnpm-lock.yaml at the workspace root, and pnpm commands work the same from any member directory. If you run socket-patch scan --mode hosted, or get <uuid> --mode hosted, with the cwd inside a member such as packages/a, hosted mode only looks for a lock in the cwd. It finds none, rewrites nothing, and exits 0 with status: success, redirected: 0. The only warning is redirect_npm_no_lockfile ("no package-lock.json / npm-shrinkwrap.json present"), which is wrong for a pnpm project: the lock exists one or two directories up.
A non-workspace project using pnpm's lockfile-dir=.. (.npmrc) / lockfileDir: .. (pnpm-workspace.yaml) gives the same result. pnpm writes the lock to the parent directory, and a hosted scan from the project directory succeeds while pinning nothing.
Impact
Repro (Linux, pnpm 12.8.1; the same on 9.15.9 / 10.34.5 / 11.28.3)
The patch API is a local mock that serves a free patch for pkg:npm/is-number@7.0.0 (batch, by-package, view, package grant, and the hosted tarball). SOCKET_PATCH_SERVER_URL points at the mock.
mkdir -p r/packages/a && cd r
echo '{"name":"r","private":true}' > package.json
printf 'packages:\n - packages/*\n' > pnpm-workspace.yaml
echo '{"name":"a","version":"1.0.0","dependencies":{"is-number":"7.0.0"}}' > packages/a/package.json
pnpm install
cd packages/a
socket-patch scan --mode hosted --json --yes --cwd . --api-url $MOCK --org test-org --api-token fake
# exit 0, status success, redirected 0, warnings [redirect_npm_no_lockfile]
socket-patch get <uuid> --mode hosted --json --yes --cwd . ... # same
cd ../.. && git diff --stat pnpm-lock.yaml # unchanged
# a fresh checkout + pnpm install --frozen-lockfile -> packages/a/node_modules/is-number is upstream
socket-patch scan --mode hosted --json --yes --cwd . ... # from the root: redirected 1 (works)
The lockfile-dir variant:
mkdir -p top/proj && cd top/proj
echo '{"name":"proj","version":"1.0.0","dependencies":{"is-number":"7.0.0"}}' > package.json
echo 'lockfile-dir=..' > .npmrc # pnpm 10+: also `lockfileDir: ..` in pnpm-workspace.yaml
pnpm install # writes ../pnpm-lock.yaml
socket-patch scan --mode hosted --json --yes --cwd . ... # exit 0, success, redirected 0, redirect_npm_no_lockfile
Expected vs actual
- Expected: one of these. (a) Hosted mode finds the governing lock the way pnpm does: the nearest ancestor
pnpm-workspace.yaml, or a configured lockfile-dir. Or (b) it refuses, with a non-success status and a pnpm-specific diagnostic that says to run from the directory holding pnpm-lock.yaml, the way vendored mode already fails closed (vendor_lockfile_missing). CLI_CONTRACT treats a found patch that wasn't applied as a non-success outcome, and the warning should name the right package manager. The code already does that for redirect_pnpm_no_lockfile when node_modules/.modules.yaml is present.
- Actual:
success, exit 0, nothing pinned, and an npm-specific "no package-lock.json" warning. The member's node_modules/.modules.yaml isn't there (pnpm keeps it at the root), so even the pnpm-named warning doesn't fire.
OS × version
| OS |
pnpm |
member cwd: scan |
member cwd: get <uuid> |
lockfile-dir=.. |
from root (control) |
| Linux |
9.15.9 |
fail (2/2) |
fail |
fail |
pass |
| Linux |
10.34.5 |
fail |
fail |
fail |
pass |
| Linux |
11.28.3 |
fail |
fail |
n/a |
pass |
| Linux |
12.8.1 |
fail (2/2) |
fail |
fail |
pass |
| macOS / Windows |
— |
untested (path-independent logic, expected the same) |
|
|
|
First bad version: not a regression. Release 4.0.0 behaves the same, and so does main 203e092.
Suspect code
crates/socket-patch-core/src/hosted/engine.rs:404 (read_candidate_files) reads lock candidates relative to --cwd only. There's no ancestor or lockfile-dir lookup.
crates/socket-patch-core/src/patch/redirect/mod.rs:826-857 falls through to redirect_npm_no_lockfile and keeps the run a success.
Related: #417 (cargo, same shape), #492 (pnpm per-package locks).
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
pnpm keeps one
pnpm-lock.yamlat the workspace root, and pnpm commands work the same from any member directory. If you runsocket-patch scan --mode hosted, orget <uuid> --mode hosted, with the cwd inside a member such aspackages/a, hosted mode only looks for a lock in the cwd. It finds none, rewrites nothing, and exits 0 withstatus: success,redirected: 0. The only warning isredirect_npm_no_lockfile("no package-lock.json / npm-shrinkwrap.json present"), which is wrong for a pnpm project: the lock exists one or two directories up.A non-workspace project using pnpm's
lockfile-dir=..(.npmrc) /lockfileDir: ..(pnpm-workspace.yaml) gives the same result. pnpm writes the lock to the parent directory, and a hosted scan from the project directory succeeds while pinning nothing.Impact
get <uuid>asks for it, but nothing gets pinned. Exit 0 andsuccessmean CI and users think the project is protected.pnpm install --frozen-lockfileinstalls the vulnerable upstream bytes.vendor_lockfile_missing,partial_failure), so the two modes disagree. Hosted is the only one that reports success.Repro (Linux, pnpm 12.8.1; the same on 9.15.9 / 10.34.5 / 11.28.3)
The patch API is a local mock that serves a free patch for
pkg:npm/is-number@7.0.0(batch, by-package, view, package grant, and the hosted tarball).SOCKET_PATCH_SERVER_URLpoints at the mock.The
lockfile-dirvariant:Expected vs actual
pnpm-workspace.yaml, or a configuredlockfile-dir. Or (b) it refuses, with a non-success status and a pnpm-specific diagnostic that says to run from the directory holdingpnpm-lock.yaml, the way vendored mode already fails closed (vendor_lockfile_missing). CLI_CONTRACT treats a found patch that wasn't applied as a non-success outcome, and the warning should name the right package manager. The code already does that forredirect_pnpm_no_lockfilewhennode_modules/.modules.yamlis present.success, exit 0, nothing pinned, and an npm-specific "no package-lock.json" warning. The member'snode_modules/.modules.yamlisn't there (pnpm keeps it at the root), so even the pnpm-named warning doesn't fire.OS × version
lockfile-dir=..First bad version: not a regression. Release 4.0.0 behaves the same, and so does main
203e092.Suspect code
crates/socket-patch-core/src/hosted/engine.rs:404(read_candidate_files) reads lock candidates relative to--cwdonly. There's no ancestor orlockfile-dirlookup.crates/socket-patch-core/src/patch/redirect/mod.rs:826-857falls through toredirect_npm_no_lockfileand keeps the run a success.Related: #417 (cargo, same shape), #492 (pnpm per-package locks).