Skip to content

Hosted scan/get run from a vlt workspace member reports success while pinning nothing: the #598 / #901 member refusal has no vlt.json case #942

Description

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

Summary

vlt keeps one vlt-lock.json at the root of a workspace declared in vlt.json ("workspaces": [...]). Run from a member directory, vlt install / vlt ci walk up to that root and write or read only the root lock. The member gets no lock of its own.

When socket-patch scan (hosted is the default) or get <uuid> --mode hosted runs from that member directory, it finds the patch for the member's node_modules/left-pad. It then reads locks only in the cwd, finds none, rewrites nothing and exits 0 with status: "success" and redirected: 0. The only signal is redirect_npm_no_lockfile ("no package-lock.json / npm-shrinkwrap.json present"), which names the wrong package manager.

This is the #590 / #884 shape. #598 added the governing-root refusal for pnpm and cargo, and open PR #901 extends it to npm, yarn and Bun package.json workspaces. But governing_root.rs has no case for a vlt workspace root. PR #901 states the exclusion directly: "pnpm reads only pnpm-workspace.yaml and vlt only vlt.json, so their locks at a workspaces root govern no member through that field; the pnpm check owns pnpm workspaces". Nothing owns vlt workspaces. Vendored mode in the same directory already fails closed (vendor_lockfile_missing, exit 1), so the two modes disagree.

Impact

  • The patch is found, and an explicit get <uuid> --mode hosted asks for it, but nothing is pinned. Exit 0 and success tell CI and users the project is protected.
  • The next vlt ci (from the member or the root) installs the vulnerable upstream bytes.
  • Nothing is written, so it's fail-safe, but it's silent.

Repro (Linux, Node 22.22.0, main 9c43dfc)

The mock is the run-2 probe mock from ledger #307 (registry on :18555, patch API on :18556, a free patch for pkg:npm/left-pad@1.3.0). vlt is run with --allow-scripts :scripts only because the sandbox blocks vlt's security-data fetch.

export SOCKET_PATCH_SERVER_URL=http://127.0.0.1:18556 SOCKET_API_URL=http://127.0.0.1:18556 \
       SOCKET_ORG_SLUG=test-org SOCKET_API_TOKEN=fake LANG=C
mkdir -p ws/packages/a && cd ws
echo '{"name":"root","version":"1.0.0","private":true}' > package.json
echo '{"workspaces":["packages/*"],"config":{"registries":{"npm":"http://127.0.0.1:18555/"}}}' > vlt.json
echo '{"name":"a","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > packages/a/package.json
(cd packages/a && vlt install)        # writes ./vlt-lock.json at the ROOT; packages/a has no lock
cd packages/a
socket-patch scan --yes --json        # rc 0, status success, redirect.redirected 0, warnings [redirect_npm_no_lockfile]
socket-patch get aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa --mode hosted --yes --json   # same: rc 0, redirected 0
socket-patch scan --mode vendored --yes --json   # rc 1, vendor_lockfile_missing (fails closed)
cd ../.. && rm -rf node_modules packages/a/node_modules
(cd packages/a && vlt ci && node -e "console.log(require('left-pad'))")   # pristine
# control: the same scan from the root redirects 1 and `vlt ci` installs `patched`

Expected vs actual

  • Expected: CLI_CONTRACT.md (hosted refusals, redirect_pnpm_lockfile_elsewhere / cargo_manifest_not_workspace_root): when "the project directory is a workspace member whose lock lives in another directory, so the rewriters, which read only the project directory, would pin nothing", the run is "refused before any takeover or write … the message names the directory to run from; exit 1". A vlt workspace member is exactly that case.
  • Actual: exit 0, status: success, redirected: 0, and only redirect_npm_no_lockfile.

Matrix

OS vlt hosted scan from member get --mode hosted from member vendored from member hosted scan from root (control)
Linux 1.2.0 fail (rc 0, nothing pinned) fail refused (rc 1) pass
Linux 1.3.7 fail (×2: run with and without tar.br alternates) fail refused (rc 1) pass

macOS and Windows weren't run (probe branches are blocked for this routine). The check is path logic, so it's OS-independent.

First bad

Not a regression. Every release since vlt support behaves this way. It became a contract gap once #598 made member runs a refusal.

Suspect code

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

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions