Skip to content

Hosted pnpm vex attests not_affected over an unpatched install when pnpm's modulesDir is set (pnpm 10.12+), because the missed install is treated as "nothing installed" #696

Description

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

Summary

On pnpm 10.12+ with modulesDir set (modulesDir: deps in pnpm-workspace.yaml, or modules-dir=deps in .npmrc on pnpm 10), pnpm keeps the virtual store at <modulesDir>/.pnpm. The npm crawler never looks there (the root cause of #661). In agent mode #661 shows up as "not installed". In hosted mode it is worse: standalone socket-patch vex reads the "nothing installed" result as a lockfile-only checkout and attests the hosted pin as not_affected, while the copy pnpm installed and Node loads (deps/.pnpm/left-pad@1.3.0/...) is still the upstream, unpatched file.

The same project with the default node_modules is handled honestly: vex finds the upstream copy, omits the patch and exits 1.

Impact

A false security attestation. The usual hosted workflow is scan --mode hosted, commit, then socket-patch vex as the post-install check that the redirect warning recommends ("Run socket-patch vex after installation to verify the patched files"). With modulesDir, that check publishes not_affected for a vulnerable install, for example over a warm node_modules/store, which the same warning says can still hold upstream files.

Repro

Uses a local mock of the patch API for pkg:npm/left-pad@1.3.0 (batch, package grant, view, hosted tarball; the patch prepends a marker to index.js), like e2e_redirect_pnpm_build.rs.

export SOCKET_NO_CONFIG=1 SOCKET_NO_UPDATE_CHECK=1 SOCKET_PATCH_SERVER_URL=$API
API_ARGS="--api-url $API --org test-org --api-token fake"
mkdir p && cd p
echo '{"name":"p","version":"0.0.0","private":true,"dependencies":{"left-pad":"1.3.0"}}' > package.json
printf 'modulesDir: deps\n' > pnpm-workspace.yaml   # pnpm 10: echo modules-dir=deps > .npmrc
pnpm install                                       # -> deps/.pnpm/left-pad@1.3.0 (upstream bytes)
socket-patch scan --mode hosted --json --yes $API_ARGS   # success, lock repointed (+ trustLockfile on 11+)
head -c 30 deps/.pnpm/left-pad@1.3.0/node_modules/left-pad/index.js   # "/* This program is free softwa"  (upstream, no marker)
socket-patch vex --json $API_ARGS --output vex.json; echo $?          # 0
grep '"status"' vex.json                                              # "not_affected", "Patched via Socket patch … (redirected)"

Control: delete the modulesDir line and repeat. vex exits 1 with no_applicable_patches (the installed upstream copy fails verification), which is the correct answer.

Expected vs actual

crates/socket-patch-cli/CLI_CONTRACT.md, "Manifest-less VEX", hosted row: "The installed copies the build consumes … are hash-verified when any exist … Installed evidence wins: hash_mismatch / not_applied are omitted. With nothing installed, a discovered reference whose lock pins the artifact … attests from that pin … because 'not installed' has to mean the crawler looked."

  • Expected: the installed deps/.pnpm copy is verified, its upstream bytes fail, and the patch is omitted (exit 1), as with the default modules dir.
  • Actual: the crawler never looks in deps/, the patch comes back package_not_found, the lockfile-basis exemption turns that into an attestation, and the run prints not_affected with exit 0.

Matrix (Linux, Node 22, main 045d7ec)

pnpm modulesDir standalone vex over the upstream install
9.15.9 modules-dir=deps pass (store stays in node_modules/.pnpm; exit 1, nothing attested)
10.11.1 modules-dir=deps pass (store stays in node_modules/.pnpm)
10.12.0 modules-dir=deps fail: not_affected, exit 0
10.34.5 modules-dir=deps fail (reproduced 3×)
11.28.3 modulesDir: deps fail (3×)
12.8.1 modulesDir: deps fail (4×)
10.34.5 / 12.8.1 default pass (exit 1, nothing attested)

Release 4.0.0 (npm @socketsecurity/socket-patch) is honest in every modulesDir cell (exit 1, package_not_found). macOS and Windows are untested, but the crawler logic isn't OS-specific.

Not affected, for comparison: a transitive dep under a custom virtualStoreDir: .vstore (pnpm 10.34.5 / 12.8.1) is still found, so vex is honest there.

Separate and documented: in-run scan --mode hosted --vex attests from this run's records without hash verification (contract table, (redirected) row), so it says not_affected in both layouts. This issue is about the post-install standalone vex.

First bad commit

Bisected between v4.0.0 and 045d7ec (oracle: standalone vex on the 12.8.1 modulesDir fixture above): cf8150b "feat(vex): manifest-less VEX from hosted/vendored lockfiles … (#251)", which added the nothing-installed lockfile-basis attestation.

Suspect code

Related: #661 (agent apply, same blind spot), #686 (the same false-attestation shape for Composer vendor-dir).

No probe runs: macOS/Windows probes are on hold (see ledger #303).

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