[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).
Summary
If .socket/vendor/npm is a symlink (for example, two non-workspace projects in a monorepo that share one vendor store), scan --mode vendored writes the <uuid>/ artifact dirs through the link. rollback / vendor --revert then delete through the link too (remove_tree_and_prune at crates/socket-patch-core/src/vendor/yarn_classic_lock.rs:548). When a second project has vendored the same patch into the same shared store, rolling back the first project deletes the second project's tarballs. The second project's yarn.lock still points at file:./.socket/vendor/npm/<uuid>/…tgz, so its next frozen or offline install fails. The rollback exits success with no warning.
The code already says this layout isn't ours. sweep_vendor_dirs (crates/socket-patch-core/src/vendor/path.rs ~357–364 and the note at ~892) is deliberately symlink-strict: "staging creates these dirs itself and never writes symlinks, so a symlinked eco dir cannot be ours — read_dir would follow it and the sweep would enumerate (and let callers delete through) its target." The vendor write path and the ledger-driven revert don't apply that rule, so the delete-through the sweep guards against still happens.
Impact
- Silent data loss in a different project: a project that is correctly vendored loses its committed artifacts, and the
rollback that did it reports success.
- That project's
yarn install --frozen-lockfile [--offline] then fails (Tarball is not in network and can not be located in cache). Its vendor --check reports artifact verification failed: Missing.
Repro (yarn 1.22.22; local mock patch API serving is-number@7.0.0 and left-pad@1.3.0 patches)
mkdir -p mono/shared && cd mono
for X in A B; do
mkdir $X && (cd $X
echo '{"name":"'$X'","version":"1.0.0","private":true,"dependencies":{"is-number":"7.0.0","left-pad":"1.3.0"}}' > package.json
yarn install
mkdir -p .socket/vendor && ln -s ../../../shared .socket/vendor/npm
socket-patch scan --mode vendored --vendor-source service --api-url $API --org test-org --api-token x --json --yes) # success
done
find shared -type f | wc -l # 4 (two uuid dirs: marker + tgz each)
(cd A && socket-patch rollback --json --yes) # status: success, no warning
find shared -type f | wc -l # 0 <- B's artifacts are gone too
cd B && rm -rf node_modules && yarn install --frozen-lockfile --offline
# error "./.socket/vendor/npm/1111…/is-number-7.0.0.tgz": Tarball is not in network and can not be located in cache (exit 1)
socket-patch vendor --check --json # partialFailure: artifact verification failed: Missing (x2)
vendor --revert in A instead of rollback gives the same result.
Expected vs actual
- Expected: a symlinked
.socket/vendor/<eco> (or <uuid>) dir is refused before any write, as the other symlinked vendored inputs are. Hosted redirect_symlinked_file_unsupported, the cargo / pypi *_symlink_unsupported refusals and the bun / hatch backends' own checks all refuse before writing (CLI_CONTRACT error table). At minimum, a revert must never delete through a link the sweep treats as "cannot be ours".
- Actual: vendoring writes through the link, and the revert deletes the uuid dirs at the link's target. Exit 0 /
success both ways.
Matrix (Linux, main 045d7ec, Node 22)
| yarn |
A + B vendored |
A rollback empties the shared store |
B frozen offline install afterwards |
| 1.7.0 |
success |
yes |
fails (exit 1) |
| 1.10.1 |
success |
yes |
fails (exit 1) |
| 1.22.22 |
success (x2) |
yes (x2, also via vendor --revert) |
fails (exit 1) |
Single project with a symlinked .socket/vendor/npm: the scan, the fresh frozen install, vendor --check and rollback all "work" (the artifacts live at the link target). The damage only shows when the store is shared. A whole-.socket symlink shares vendor/state.json too, so that's a different shape; it wasn't tested for cross-project effects.
Suspect code
No probe run: the behaviour comes from a path-handling choice that doesn't depend on the OS. macOS / Windows (directory junctions) are untested.
[agent] Found by the scheduled Yarn classic (1.x) bug-hunt routine (ledger #304).
Summary
If
.socket/vendor/npmis a symlink (for example, two non-workspace projects in a monorepo that share one vendor store),scan --mode vendoredwrites the<uuid>/artifact dirs through the link.rollback/vendor --revertthen delete through the link too (remove_tree_and_pruneatcrates/socket-patch-core/src/vendor/yarn_classic_lock.rs:548). When a second project has vendored the same patch into the same shared store, rolling back the first project deletes the second project's tarballs. The second project'syarn.lockstill points atfile:./.socket/vendor/npm/<uuid>/…tgz, so its next frozen or offline install fails. The rollback exitssuccesswith no warning.The code already says this layout isn't ours.
sweep_vendor_dirs(crates/socket-patch-core/src/vendor/path.rs~357–364 and the note at ~892) is deliberately symlink-strict: "staging creates these dirs itself and never writes symlinks, so a symlinked eco dir cannot be ours —read_dirwould follow it and the sweep would enumerate (and let callers delete through) its target." The vendor write path and the ledger-driven revert don't apply that rule, so the delete-through the sweep guards against still happens.Impact
rollbackthat did it reports success.yarn install --frozen-lockfile [--offline]then fails (Tarball is not in network and can not be located in cache). Itsvendor --checkreportsartifact verification failed: Missing.Repro (yarn 1.22.22; local mock patch API serving is-number@7.0.0 and left-pad@1.3.0 patches)
vendor --revertin A instead ofrollbackgives the same result.Expected vs actual
.socket/vendor/<eco>(or<uuid>) dir is refused before any write, as the other symlinked vendored inputs are. Hostedredirect_symlinked_file_unsupported, the cargo / pypi*_symlink_unsupportedrefusals and the bun / hatch backends' own checks all refuse before writing (CLI_CONTRACT error table). At minimum, a revert must never delete through a link the sweep treats as "cannot be ours".successboth ways.Matrix (Linux, main
045d7ec, Node 22)rollbackempties the shared storevendor --revert)Single project with a symlinked
.socket/vendor/npm: the scan, the fresh frozen install,vendor --checkand rollback all "work" (the artifacts live at the link target). The damage only shows when the store is shared. A whole-.socketsymlink sharesvendor/state.jsontoo, so that's a different shape; it wasn't tested for cross-project effects.Suspect code
crates/socket-patch-core/src/vendor/yarn_classic_lock.rs:548:remove_tree_and_prune(&uuid_dir, …)with no lstat check on.socket/vendor/npmor on<uuid>..socket/vendor/<eco>/<uuid>/has no symlink gate. The other npm-family backends (npm_lock,pnpm_lock,bun_lock,vlt_lock) and composer / nuget use the sameremove_tree_and_prune(&uuid_dir, …)pattern, so this probably isn't specific to yarn classic. Related: Vendored yarn classic replaces a symlinked yarn.lock with a regular file (hosted refuses the same lock), leaving the link's target unpatched; rollback never restores the link #627 (symlinkedyarn.lockreplaced by vendored mode).No probe run: the behaviour comes from a path-handling choice that doesn't depend on the OS. macOS / Windows (directory junctions) are untested.