Skip to content

vlt hosted rollback, remove and the hosted→vendored takeover restore a vlt 1.3 brotli node as a gzip node (flag bit 4, the .tar.br integrity and URL are lost) #941

Description

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

Summary

#820 (the fix for #372) made socket-patch accept vlt 1.3 lock nodes that carry the brotli flag (slot [0] bit 4). A hosted pin now correctly clears bit 4 when it points the node at the patched .tgz. But the hosted upstream restore used by rollback, remove and the hosted→vendored takeover (crates/socket-patch-core/src/patch/redirect/upstream/vlt.rs) never puts the brotli form back. It rebuilds slot [2] from the registry's dist.integrity and slot [3] from the conventional .tgz URL, then derives bit 4 from that .tgz URL (brotli_for_slot3(slot3) at upstream/vlt.rs:272), so the bit is always cleared. Nothing in the restore path reads the packument's dist.alternates (grep -rn alternates crates/ finds nothing).

So after a rollback, the node vlt originally wrote as

"~npm~left-pad@1.3.0": [4,"left-pad","sha512-p3wq…(tar.br)","http://127.0.0.1:18555/left-pad/-/left-pad-1.3.0.tar.br"]

comes back as

"~npm~left-pad@1.3.0": [0,"left-pad","sha512-ICFp…(tgz)","http://127.0.0.1:18555/left-pad/-/left-pad-1.3.0.tgz"]

A dev node goes from 6 to 2 the same way. The #372 triage comment had already flagged that rollback/remove "will need to put bit 4 back if the hosted rewrite clears it", and the #820 commit message says "reverts put the recorded bit back with the original slots". That holds for the vendored revert (byte-exact in my runs) and for carried_pin_original, but not for the hosted upstream restore.

Impact

  • rollback / remove don't undo the scan: vlt-lock.json differs from the committed, pre-scan lock in slots [0], [2] and [3], so users see an unexplained lock diff after a "successful" rollback.
  • vlt doesn't heal it. vlt install and vlt ci keep the gzip node indefinitely (checked on 1.3.6 and 1.3.7), so the project silently loses vlt 1.3's Brotli tarball selection for that package until someone deletes the lock or runs vlt update.
  • The install itself still works: the restored node is self-consistent, and vlt ci installs pristine bytes. So this isn't a security or availability issue. It's a broken rollback contract for vlt ≥ 1.3.1 projects on registries that advertise tar.br alternates (e.g. vlt's own registry).

Repro (Linux, Node 22.22.0, main 9c43dfc)

The mock is the run-2 probe mock from ledger #307: mock.mjs serves a registry on :18555 plus the patch API on :18556. The left-pad@1.3.0 packument advertises dist.alternates: [{kind:"tar.br", …}], and the mock serves a free hosted patch. vlt is run with --allow-scripts :scripts only because the sandbox blocks vlt's security-data fetch.

PORT=18555 node mock.mjs & PORT=18556 node mock.mjs &
export SOCKET_PATCH_SERVER_URL=http://127.0.0.1:18556 SOCKET_API_URL=http://127.0.0.1:18556 \
       SOCKET_NPM_REGISTRY=http://127.0.0.1:18555 SOCKET_ORG_SLUG=test-org SOCKET_API_TOKEN=fake LANG=C
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
vlt install && cp vlt-lock.json orig
grep '~npm~left-pad' orig        # [4,"left-pad","sha512-p3wq…",".../left-pad-1.3.0.tar.br"]
socket-patch scan --yes --json    # rc 0, redirected 1, node -> [0,…,patched .tgz]  (#372 fixed)
socket-patch rollback --yes --json   # rc 0, status success, hosted.reverted [pkg:npm/left-pad@1.3.0]
cmp orig vlt-lock.json            # differ
grep '~npm~left-pad' vlt-lock.json   # [0,"left-pad","sha512-ICFp…",".../left-pad-1.3.0.tgz"]
vlt install && grep '~npm~left-pad' vlt-lock.json   # still [0,…,.tgz]: vlt keeps the gzip node

The same happens with socket-patch remove pkg:npm/left-pad@1.3.0, and with scan --mode vendored over the hosted pin followed by rollback (the takeover's upstream restore writes the gzip node, and the vendored ledger then records that gzip node as the original). For a brotli dev node, flag 6 comes back as 2.

For comparison, a straight scan --mode vendored followed by rollback on the same brotli lock is byte-exact.

Expected vs actual

  • Expected: docs/ecosystems.md ("Hosted vlt pins need no ledger (v5.0): rollback restores them from the npm registry") and CLI_CONTRACT.md's vlt restore say the node gets the registry's entry back. For a registry that advertises a tar.br alternate, the entry vlt 1.3 writes is the brotli one: bit 4 set, the alternate's integrity in slot [2] and its URL in slot [3]. That's what the pre-scan lock held, and what a fresh vlt install writes. Fix vlt 1.3 brotli lock nodes being refused (#372) #820 states that reverts put the recorded bit back.
  • Actual: the restore always writes the gzip dist.integrity, the .tgz URL and bit 4 cleared.

Matrix

OS vlt hosted rollback hosted remove hosted→vendored takeover + rollback vendored-only rollback
Linux 1.3.6 fail fail not run pass (byte-exact)
Linux 1.3.7 fail (×2) fail fail pass (byte-exact)
Linux 1.2.0 / any vlt, registry without tar.br pass pass pass pass

macOS and Windows weren't run (probe branches are blocked for this routine). The code path is OS-independent.

First bad

Not a regression. Before #820 (7fd88f5), brotli locks were refused outright (#372). Every commit since then restores the gzip form.

Suspect code

No activity

Activity on this issue will appear here.

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