Skip to content

vlt 1.4.0 reads workspaces from pnpm-workspace.yaml, so the #942 / #1134 member refusals miss those members: hosted and vendored scans write a stray member lock and exit 0, and vendored VEX attests not_affected #1564

Closed

Description

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

Summary

vlt 1.4.0 (published 2026-10-14) adds a third workspace source. vlt reads vlt.json workspaces, then the root package.json workspaces, and if neither is present it now reads the packages: list of pnpm-workspace.yaml (resolveWSConfig in vlt's src/workspaces; its new error text says "...the vlt.json config, the "workspaces" field of the root package.json, or the "packages" list of pnpm-workspace.yaml..."). socket-patch's vlt member checks only read the first two (vlt_workspace_root / package_json_workspace_root in hosted/governing_root.rs). As a result, a member that vlt 1.4.0 installs from the root's vlt-lock.json isn't recognised as a member:

Impact

A pnpm-to-vlt migration that keeps pnpm-workspace.yaml is exactly the layout vlt 1.4 now supports. In that layout, socket-patch reports success, nothing patched gets installed, and the vendored VEX is a false not_affected attestation.

Repro (vlt 1.4.0, main 7b6371c, Linux)

Use the mock registry and patch API from #1494 (PORT=18555 node mock.mjs & PORT=18556 node mock.mjs &), with SOCKET_API_URL / SOCKET_PATCH_SERVER_URL=http://127.0.0.1:18556, SOCKET_NPM_REGISTRY=http://127.0.0.1:18555, SOCKET_ORG_SLUG=test-org and SOCKET_API_TOKEN=fake.

# a canonical single-project lock for the stray copy
mkdir solo && cd solo
echo '{"name":"a","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
echo '{"config":{"registries":{"npm":"http://127.0.0.1:18555/"}}}' > vlt.json
vlt install --allow-scripts :scripts && cd ..

mkdir -p ws/packages/a && cd ws
echo '{"name":"root","version":"1.0.0","private":true}' > package.json
echo '{"config":{"registries":{"npm":"http://127.0.0.1:18555/"}}}' > vlt.json   # no "workspaces"
printf 'packages:\n  - "packages/*"\n' > pnpm-workspace.yaml
echo '{"name":"a","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > packages/a/package.json
vlt install --allow-scripts :scripts
grep workspace vlt-lock.json          # "workspace~packages+a left-pad": "prod 1.3.0 ~npm~left-pad@1.3.0"
cp ../solo/vlt-lock.json packages/a/   # stray member lock (omit for the #942 shape)
cd packages/a
socket-patch scan --mode vendored --yes --json   # exit 0, success; rewrites packages/a/{package.json,vlt-lock.json}
cd ../.. && vlt ci --allow-scripts :scripts      # exit 1: Lockfile is out of sync with package.json
cd packages/a && node -e "console.log(require('left-pad'))"   # pristine
socket-patch vex --output vex.json               # exit 0; statement not_affected / inline_mitigations_already_exist

With --mode hosted instead, the scan exits 0, rewrittenFiles: ["vlt-lock.json"] (the member's), the root vlt ci exits 0 and installs pristine bytes, and vex exits 1.

Expected vs actual

OS × version

Each cell was run twice.

OS vlt layout hosted vendored
Linux 1.4.0 pnpm-workspace.yaml + stray member lock fail (2/2) fail (2/2), false VEX
Linux 1.4.0 pnpm-workspace.yaml, no member lock fail: exit 0, pins nothing (2/2) pass (vendor_lockfile_missing)
Linux 1.4.0 same globs in vlt.json workspaces (control) pass (redirect_workspace_lockfile_elsewhere) pass
Linux 1.3.8 pnpm-workspace.yaml + stray member lock pass: vlt 1.3.8 ignores pnpm-workspace.yaml (the lock has no workspace~packages+a edge), so packages/a is its own project and writing its lock is correct pass (same)

First bad vlt release: 1.4.0. This isn't a socket-patch regression; the cause is vlt's new workspace source. macOS and Windows weren't probed (this routine's probe branches are blocked). The fix will probably need to be gated on vlt's version or the lock: since 1.3.8 must keep treating the member as its own project, the root lock's workspace~<member> edges could decide, as vlt_lists_member already does for the ! dialect split.

Suspect code

  • crates/socket-patch-core/src/hosted/governing_root.rs:755 vlt_workspace_root: reads only vlt.json workspaces.
  • crates/socket-patch-core/src/hosted/governing_root.rs:556: the package.json fallback adds vlt-lock.json only for a package.json workspaces root. vlt 1.4's third fallback (pnpm-workspace.yaml packages: when package.json has no workspaces) isn't modelled.
  • crates/socket-patch-core/src/hosted/governing_root.rs:106-111 (member_stray_lock_refusal / nearer_root) and crates/socket-patch-core/src/vendor/npm_flavor.rs:412 (member_stray_lock_refusal, which the vendored engine uses) inherit the gap.
  • docs/testing/vlt-compatibility.md: the Formats and Releases tables stop at 1.3.8.

Activity

  1. mikolalysenko commented on Oct 15, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged: priority:p1 (vlt, npm family). Confirmed on main e76cb9b: vlt_workspace_root (crates/socket-patch-core/src/hosted/governing_root.rs:755) reads member globs only from an ancestor vlt.json workspaces, and the package_json_workspace_root fallback (line 520) only from package.json workspaces; nothing models vlt 1.4's third source, the packages: list of pnpm-workspace.yaml. Both member_stray_lock_refusal paths (hosted and vendor/npm_flavor.rs) inherit the gap. Not a duplicate: #942 / #1134 / #1542 cover the vlt.json and package.json sources. As the report notes, the fix must stay version-aware, since vlt 1.3.8 ignores pnpm-workspace.yaml (the root lock's workspace~<member> edges can decide).


    Generated by Claude Code

  2. mikolalysenko commented on Oct 15, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue (shared root cause: vlt member detection ignores vlt 1.4's pnpm-workspace.yaml packages: workspace source). Branch: agent/fix-vlt-pnpm-workspace-members. Claim-ID: 2026-10-15T10:21:02Z-837352


    Generated by Claude Code

  3. mikolalysenko commented on Oct 15, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft fix PR: #1574


    Generated by Claude Code

  4. mikolalysenko commented on Oct 15, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Verified fixed on main 4cc50f4 (#1574) with real vlt 1.4.0, 2× per cell. From a pnpm-workspace.yaml member, hosted refuses redirect_workspace_lockfile_elsewhere and vendored refuses vendor_lockfile_missing, with and without a stray member lock. Nothing is written, and member vex has nothing to attest.

    Refusals match vlt 1.4.0's own reading across 27 yaml shapes: BOM, CRLF, block / flow / multi-line flow / column-0 items, quoted key, single quotes, ./, **, braces, extglob, trailing /, comments, --- / ... markers, multi-doc, plus ! exclusions (excluded dirs are not refused). Non-members vlt rejects (anchors, tabs, duplicate key, escapes) are not refused either. Also checked: package.json workspaces ([], {nohoist}, a listing pattern, a non-listing one) taking precedence over the yaml. Root scans of such a workspace pass in hosted, vendored and agent mode (scan → vlt ci patched, vex not_affected, re-run, byte-exact rollback).

    One residue, filed separately as #1584: on vlt 1.3.8 a yaml-listed directory isn't its own project either (vlt's root lookup goes to the nearest ancestor vlt.json), so the 1.3.8 branch still writes a stray member lock with exit 0.


    Generated by Claude Code

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