Repository navigation
Vendored vlt scan accepts a package with a preprepare or postprepare script, which vlt ci then runs inside the committed vendor directory, so vendor --check and vex fail and repair loops #1633
Description
[agent] Found by the scheduled vlt bug-hunt routine (ledger #307).
Summary
Vendored vlt mode refuses a package that vlt build would build inside the committed file: directory (vendor_vlt_build_scripts_unsupported). The check lists only preinstall, install, postinstall, prepare and binding.gyp. vlt also runs preprepare and postprepare for a file: directory dependency, and each one runs even when it is the package's only script. vlt's own nodeNeedsBuild tests scripts.prepare || scripts.preprepare || scripts.postprepare, and its --allow-scripts help lists all six scripts.
So a registry package whose only lifecycle script is postprepare (or preprepare) gets vendored with exit 0. While it came from the registry, vlt never ran that script, because registry nodes aren't "prepable". Once vendored, the next vlt ci / vlt install with an allow-scripts query that matches the package runs the script inside .socket/vendor/npm/<uuid>/<name>-<ver>/node_modules/<name>/. That rewrites the committed artifact, which is exactly what the refusal exists to prevent.
Impact
vendor --checkexits 1 withvendor_inventory_mismatchright after an ordinaryvlt ci.vexexits 1 and leaves the package out ("the vendored artifact's contents do not match its ledger inventory"), even though the installed copy is patched. The project loses its attestation.- The fix the CLI prescribes loops.
socket-patch repairredownloads the artifact andvendor --checkgoes green, but the nextvlt ciruns the script again and the check is red again. - The script's output lands in the committed tree: an untracked file, or a modified tracked file when the script edits the package. The git checkout is dirty after every install.
Repro (main 7d3086f, also release 5.0.1; vlt 1.4.0)
I used the run-19 mock from #1494, changed so left-pad@1.3.0's package.json (in both the pristine and the patched tarball, and in the packument) carries "scripts": {"postprepare": "node -e \"require('fs').writeFileSync('built.txt', require('./index.js'))\""}. The patch only changes index.js. Start it on :18555 (registry) and :18556 (patch API).
export SOCKET_API_URL=http://127.0.0.1:18556 SOCKET_PATCH_SERVER_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
mkdir p && cd p
echo '{"name":"root","version":"1.0.0","dependencies":{"left-pad":"1.3.0"}}' > package.json
echo '{"config":{"registries":{"npm":"http://127.0.0.1:18555/"},"allow-scripts":"#left-pad"}}' > vlt.json
vlt install
find node_modules/.vlt -name built.txt | wc -l # 0: vlt doesn't run postprepare for a registry node
socket-patch scan --mode vendored --yes --json # exit 0, applied (no refusal)
git init -q && git add -A && git commit -qm vendored
vlt ci # exit 0, runs postprepare in the vendor dir
git status --porcelain --ignored .socket # ?? .socket/vendor/npm/<uuid>/left-pad-1.3.0/node_modules/left-pad/built.txt
socket-patch vendor --check # exit 1: vendor_inventory_mismatch; "run `socket-patch repair`"
socket-patch vex -O vex.json # exit 1: omits pkg:npm/left-pad@1.3.0
socket-patch repair --yes && socket-patch vendor --check # exit 0
vlt ci && socket-patch vendor --check # exit 1 againControl: the same package with prepare instead of postprepare is refused, vendor_vlt_build_scripts_unsupported (exit 1, nothing written). That's the documented behaviour.
vlt itself, outside socket-patch: a file:./x dependency whose package.json has only a preprepare or only a postprepare script runs it on vlt install --allow-scripts '*' on 1.0.10, 1.2.0, 1.3.8 and 1.4.0 (the script's marker file is written each time).
Expected vs actual
- Expected (
docs/ecosystems.md, vlt notes, "Vendored: directory artifacts"): "a package with apreinstall,install,postinstallorpreparescript or abinding.gyp, whichvlt buildwould build inside the committed directory (vendor_vlt_build_scripts_unsupported); use hosted mode for those." The point of the rule is that nothingvlt buildruns may touch the committed directory. Apreprepare/postpreparepackage should get the same refusal, and the docs should name those scripts too. - Actual: vendored, exit 0. The first
vlt ciwith matchingallow-scriptscorrupts the artifact, andvendor --check/vex/repairthen fail or loop.
Hosted mode is fine with the same package: registry nodes never run prepare-family scripts, vlt ci installs the patched bytes, and rollback is byte-exact.
OS × version
| OS | vlt | postprepare only |
preprepare only |
prepare (control) |
|---|---|---|---|---|
| Linux | 1.2.0 | fail (vendored, vlt ci rewrites the artifact, check / vex exit 1) |
not run | not run |
| Linux | 1.3.8 | fail | not run | not run |
| Linux | 1.4.0 | fail (3×; repair loop shown) | fail | refused (pass) |
| Linux | 1.4.0 + release 5.0.1 | fail | not run | not run |
I didn't probe macOS or Windows, because probe branches are blocked for this routine (branch deletion is denied). The script list is OS-independent code.
First bad version
It's present since the build-script refusal was added (in release 5.0.1 and on main). I couldn't bisect further, because main's history was squashed.
Suspect code
crates/socket-patch-core/src/vendor/npm_dir.rs:757,vlt_build_steps: the list["preinstall", "install", "postinstall", "prepare"]is missingpreprepareandpostprepare. vlt'snodeNeedsBuild(src/query/src/pseudo/scripts.tsin the vlt bundle) checksprepare || preprepare || postpreparefor prepable nodes.npm_dir.rs:1161, the testvlt_build_steps_name_every_script_vlt_build_runs_in_place, pins the incomplete list.docs/ecosystems.mdvlt notes: the refusal sentence lists the same four scripts.
Activity
mikolalysenko commentedon Oct 17, 2026
[agent] Triage:
priority:p1(vlt). Confirmed onmain7d3086f:vlt_build_steps(crates/socket-patch-core/src/vendor/npm_dir.rs:759) lists onlypreinstall,install,postinstall,prepare, so a package whose only lifecycle script ispreprepareorpostpreparepasses thevendor_vlt_build_scripts_unsupportedrefusal. The testvlt_build_steps_name_every_script_vlt_build_runs_in_placeand the vlt notes indocs/ecosystems.mdpin the same four scripts. No duplicate or existing fix PR found.Generated by Claude Code
mikolalysenko commentedon Oct 18, 2026
[agent] Claiming this issue (shared root cause:
vlt_build_stepsomits thepreprepare/postpreparescripts vlt runs for afile:directory). Branch: agent/fix-vlt-prepare-family-scripts. Claim-ID: 2026-10-18T00:20:50Z-5e20acGenerated by Claude Code
mikolalysenko commentedon Oct 18, 2026
[agent] Draft fix: #1637
Generated by Claude Code
Refuse vlt vendoring of prepare-family scripts
Refuse prepare scripts in already-vendored vlt dirs