Skip to content

Hosted and vendored modes refuse vlt 1.3 locks whose nodes carry the new brotli flag (slot [0] = 4) #372

Description

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

Summary

vlt 1.3.0 added --brotli-tarballs (default on). When a registry's packument advertises dist.alternates: [{ "kind": "tar.br", ... }] for a version, vlt resolves the .tar.br artifact and records a new node flag bit, LockfileNodeFlagBrotli = 4, in slot [0] (so a node's flags can now be 4, 5, 6 or 7). vlt 1.3's lockfile code is otherwise unchanged from 1.2.0: the DepID grammar and the one-node-per-line layout are the same, and lockfileVersion stays 1.

socket-patch's strict node-line grammar only accepts slot [0] ∈ {0,1,2,3} (crates/socket-patch-core/src/vendor/vlt_lock_text.rs:673), so a node with a brotli flag doesn't parse:

  • Hosted (scan --mode hosted): when every node is brotli (the normal case for a project on such a registry), the lock is refused with redirect_vlt_lock_unsupported: "nodes section is not in vlt's canonical layout; re-save it with a current vlt (vlt install) or update socket-patch". The command exits 0, nothing is redirected, and vlt ci installs the unpatched bytes. The remedy the message gives is wrong: the lock was just written by the current vlt. In a mixed lock, the lock-level check passes (it uses .any(), patch/redirect/vlt.rs:93-98), but a brotli target still can't be located by instance_line, so that dependency is refused.
  • Vendored (scan --mode vendored / vendor): fails with vendor_lockfile_version_unsupported, "vlt-lock.json is not in vlt's canonical layout; re-save it with vlt install". The code and message are both misleading: the lockfileVersion is 1.
  • Agent mode is unaffected (apply and rollback pass: it doesn't read the lock).

Impact

A vlt ≥ 1.3.0 user on a registry that serves Brotli tarballs can't use hosted or vendored mode at all. The refusal is loud in --json, but scan --mode hosted still exits 0. vlt 1.3.0–1.3.2 are published (1.3.2 is latest). The nightly canary already flags them as unlisted (vlt-compatibility run https://github.com/SocketDev/socket-patch/actions/runs/36669570554, canary (ubuntu-latest)), but the canary's capstones run against npmjs bytes, which don't advertise alternates. So they pass on 1.3.1 and this doesn't show.

Repro (Linux, vlt 1.3.2, Node 22.22.2, main f6b7fb9)

A ~60-line Node mock (mock.mjs, serving a registry plus the patch API) serves [email protected] with dist.alternates → left-pad-1.3.0.tar.br, plus a free hosted patch. NO_ALT=1 turns the alternates off for the control. The mock is in the ledger entry.

node mock.mjs &   # PORT=18555
mkdir p && cd p
echo '{"name":"app","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
echo '{"config":{"registries":{"npm":"http://127.0.0.1:18555/"}}}' > vlt.json
LANG=C vlt install
grep '~npm~left-pad' vlt-lock.json
#   "[email protected]": [4,"left-pad","sha512-p3Lt…","http://127.0.0.1:18555/left-pad/-/left-pad-1.3.0.tar.br"]
socket-patch scan --mode hosted --yes --api-url http://127.0.0.1:18555 --org test-org --api-token fake --json
#   rc=0, "redirected": 0, warnings: [redirect_vlt_lock_unsupported "nodes section is not in vlt's canonical layout …"]
rm -rf node_modules && vlt ci && node -e "console.log(require('left-pad'))"   # pristine
socket-patch scan --mode vendored --yes --api-url http://127.0.0.1:18555 --org test-org --api-token fake --json
#   rc=1, errorCode vendor_lockfile_version_unsupported "vlt-lock.json is not in vlt's canonical layout; re-save it with `vlt install`"

Control (same mock with NO_ALT=1, so the node flag is 0): hosted redirects 1 dependency, and vlt install then gives patched.

Expected vs actual

  • Expected: docs/ecosystems.md "npm: vlt notes" says a lock is refused only when socket-patch "cannot read [it] exactly as vlt does (a UTF-8 BOM, another lockfileVersion, a nodes section outside vlt's one-node-per-line layout)". This lock has none of those. It's canonical vlt output with lockfileVersion: 1.
  • Actual: refused as non-canonical, with a wrong remedy (hosted) and a wrong error code (vendored).

What a fix must also handle (verified with real vlt 1.3.2)

I hand-applied the pin a hosted rewrite would write to the brotli node (patched sha512 in slot [2], the hosted .tgz URL in slot [3]):

slot [0] kept as vlt ci installs lock after vlt ci
4 patched rewritten: vlt recomputes brotli from the URL and saves [0,…]
0 (bit 4 cleared) patched byte-identical; vlt install --frozen-lockfile is byte-stable too

So the hosted rewrite should clear bit 4 when it points a node at the .tgz artifact, and the slot revert should restore it. Also, vlt_heal::reinstalls_after_removal (patch/redirect/vlt_heal.rs:199, matches!(flags, Some(0 | 2))) treats a prod or dev brotli node (4, 6) like an optional one and keeps the stale copy. It probably wants flags & 1 == 0 once bit 4 is known. VEX discovery goes through the same line parser (vex/discover), so it likely skips these nodes as well.

Matrix

OS vlt brotli alternates advertised hosted vendored agent
Linux 1.2.0 yes (ignored, flag 0) pass untested untested
Linux 1.3.1 yes fail (refused) fail untested
Linux 1.3.2 yes fail (refused) fail pass
Linux 1.3.2 no (control) pass untested untested
macOS / Windows 1.3.x yes untested (pure text-parser path, expected identical) untested untested

vlt 1.3.0 itself fails vlt install with "Integrity check failure" against a registry advertising alternates (a vlt bug, fixed in 1.3.1), so 1.3.0 locks with bit 4 are unlikely in practice.

First bad: vlt 1.3.0 (first release with LockfileNodeFlagBrotli). Release 4.0.0 of socket-patch predates vlt hosted support (it reports redirect_npm_no_lockfile), so this isn't a socket-patch regression.

Suspect code: crates/socket-patch-core/src/vendor/vlt_lock_text.rs:673 (the "0" | "1" | "2" | "3" whitelist), crates/socket-patch-core/src/patch/redirect/vlt.rs:93-103, crates/socket-patch-core/src/patch/redirect/vlt_heal.rs:199.

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