Repository navigation
Hosted pnpm scan refuses with redirect_pnpm_lockfile_disabled over env lockfile settings the project's pnpm major ignores (e.g. any pnpm 11/12 project run through npm exec with package-lock=false) #1460
Description
[agent] Found by the scheduled pnpm bug-hunt routine (ledger #303).
Summary
The #1074 fix (e8b6adc, PR #1431) reads pnpm's lockfile setting from the file the project's pnpm major reads: YAML on pnpm 11+, .npmrc on pnpm ≤10. The environment fallback doesn't do this. lockfile_disabled always accepts both pnpm_config_lockfile and npm_config_lockfile, and falls back to npm_config_package_lock, whatever the major. Real pnpm reads only one family of variables:
variable =false |
pnpm 9.15.9 | pnpm 10.34.6 | pnpm 11.28.3 | pnpm 12.11.2 |
|---|---|---|---|---|
npm_config_lockfile |
— | lock off | lock used | lock used |
pnpm_config_lockfile |
lock used | lock used | lock off | lock off |
npm_config_package_lock (= empty, npm's spelling of false) |
— | — | lock used | lock used |
(The "lock used" cells were checked by running pnpm install with the variable set and seeing whether pnpm-lock.yaml was written. Every cell where socket-patch refuses falls under "lock used", apart from the two "off" controls listed below.)
So hosted mode leaves the lock unpinned and warns redirect_pnpm_lockfile_disabled for projects whose pnpm installs from the lock just fine. The refusal fires when it shouldn't.
The realistic trigger needs no CI variable at all. npm exec / npx / npm run export npm_config_package_lock= whenever a project or user .npmrc has package-lock=false, which many repos migrated from npm still carry and which pnpm 11+ ignores. Running npx @socketsecurity/socket-patch scan --mode hosted in such a pnpm 11/12 project gets refused. A direct run of the same binary on the same tree pins it.
Impact
Hosted mode silently does nothing for these projects (exit 0, status: success). The warning tells the user that pnpm ignores their lock and suggests --mode vendored or "remove the setting", which is false for their pnpm. The result also depends on how the CLI was launched (npx vs direct), not on the project.
Repro (Linux, main 15c2c83, pnpm 12.11.2, local mock of the patch API serving left-pad@1.3.0, same contract as tests/e2e_redirect_pnpm_build.rs)
mkdir app && cd app
echo '{"name":"app","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
echo 'package-lock=false' > .npmrc # ignored by pnpm 11/12
pnpm install # pnpm 12.11.2 writes pnpm-lock.yaml (lockfileVersion 9.0)
A="--api-url http://127.0.0.1:8765 --org test-org --api-token fake"
npm exec -c "socket-patch scan --mode hosted --json --yes $A"
# exit 0, warnings: [redirect_pnpm_lockfile_disabled]
# detail: "`npm_config_package_lock=` in the environment makes pnpm neither read nor write pnpm-lock.yaml ..."
# pnpm-lock.yaml unchanged
socket-patch scan --mode hosted --json --yes $A
# exit 0, warnings: [redirect_pnpm_trust_lockfile], lock pinned to the hosted tarball
rm -rf node_modules
npm_config_package_lock= pnpm install --frozen-lockfile --store-dir /tmp/fresh
head -1 node_modules/left-pad/index.js # /* SOCKET-PATCHED */ : pnpm 12 honours the lock with that env setThe minimal form is npm_config_lockfile=false socket-patch scan --mode hosted ... on any pnpm 11/12 project, or pnpm_config_lockfile=false ... on any pnpm 9/10 project.
Expected vs actual
- Expected: CLI_CONTRACT.md "pnpm with its lockfile off (Hosted pnpm scan pins a pnpm-lock.yaml that pnpm is configured to ignore (
lockfile=false/lockfile: false), reports success, and lock-only VEX attests not_affected while pnpm installs the upstream package #1074)" says "Each pnpm major reads the setting from one file". The refusal is meant to fire only when pnpm "neither read[s] nor write[s] pnpm-lock.yaml". The environment should follow the same major split:npm_config_*for pnpm ≤10 andpnpm_config_*for pnpm ≥11 (both only when the major is unknown, matching the file rule). - Actual: either variable family, plus
npm_config_package_lock, refuses on every major.
OS × version
| cell | result |
|---|---|
Linux, pnpm 12.11.2, npm_config_package_lock= (direct and via npm exec + .npmrc package-lock=false) |
false refusal (2× each) |
Linux, pnpm 12.11.2, npm_config_lockfile=false |
false refusal |
Linux, pnpm 11.28.3, npm_config_lockfile=false |
false refusal |
Linux, pnpm 10.34.6, pnpm_config_lockfile=false |
false refusal |
Linux, pnpm 9.15.9, pnpm_config_lockfile=false |
false refusal |
Linux, pnpm 11.28.3, pnpm_config_lockfile=false |
correct refusal (control) |
Linux, pnpm 10.34.6, npm_config_lockfile=false |
correct refusal (control) |
| Linux, pnpm 12.11.2, no env | pins (control) |
macOS and Windows weren't probed. The logic is OS-independent.
First bad commit
e8b6adc (#1431). Release 5.0.0 predates the refusal.
Suspect code
crates/socket-patch-core/src/utils/pnpm_workspace.rs:600-603: theenvarm oflockfile_disabledrunsenv_settingbefore the major is known and returns it via.or(env)regardless ofmajor.crates/socket-patch-core/src/utils/pnpm_workspace.rs:128(env_setting) triespnpm_config_<key>thennpm_config_<key>for every caller.view_setting(:145) uses it the same way forgitBranchLockfile/sharedWorkspaceLockfile, so those env lookups probably have the same major blindness. I haven't verified that.
Not a VEX issue: lock-only vex calls lockfile_disabled(view, false).
Activity
mikolalysenko commentedon Oct 13, 2026
[agent] The
view_settingsuspect is now verified forgitBranchLockfile(main15c2c83, pnpm 12.11.2, Linux).pnpm 12 ignores
npm_config_git_branch_lockfile=true:pnpm install --lockfile-onlyon branchfeatwrites onlypnpm-lock.yamland neverpnpm-lock.feat.yaml.pnpm_config_git_branch_lockfile=truedoes create the branch lock. Yet with a leftoverpnpm-lock.feat.yamlin the tree,npm_config_git_branch_lockfile=true socket-patch scan --mode hostedrefuses withredirect_pnpm_git_branch_lockfileand leaves the lock unpinned, the same as thepnpm_config_spelling does. Same root cause:env_settingaccepts both prefixes on every pnpm major. The fix should probably coverview_setting's callers (gitBranchLockfile,sharedWorkspaceLockfile) along withlockfile_disabled.Generated by Claude Code
mikolalysenko commentedon Oct 13, 2026
[agent] Claiming this issue (shared root cause:
env_settingaccepts bothpnpm_config_*andnpm_config_*on every pnpm major, forlockfile/package-lockand forview_setting'sgitBranchLockfile/sharedWorkspaceLockfile). Triaged p1 (pnpm). Branch: agent/fix-pnpm-env-setting-major. Claim-ID: 2026-10-13T01:21:07Z-c7dddaGenerated by Claude Code
mikolalysenko commentedon Oct 13, 2026
[agent] Draft fix PR: #1461
Generated by Claude Code
Read pnpm env settings for the project's major
Skip the pnpm major read with no env setting
mikolalysenko commentedon Oct 13, 2026
[agent] Verified fixed on main
4353a7c(includes #1461), Linux, real pnpm 9.15.9 / 10.34.6 / 11.28.3 / 12.11.2, withnode_modules/.modules.yamlpresent. For each major I set each ofnpm_config_lockfile,pnpm_config_lockfile,npm_config_package_lockandpnpm_config_package_locktofalse. I then compared real pnpm (doespnpm installwrite a lock?) withscan --mode hosted:npm_config_*(lockfile / package_lock)pnpm_config_*(lockfile / package_lock)redirect_pnpm_lockfile_disabled✔All 16 cells match. One remaining case is by design: in a lockfile-only checkout with no
packageManagerand no two-document lock, the major is unknown, so both families are still read. That matches the "both only when the major is unknown" rule in this issue's Expected section. A real pnpm 11/12 install with apackageManagerordevEngines.packageManagerpin writes the two-document lock, and the major is then detected correctly.Generated by Claude Code
chore(release): 5.0.1 (#1485)