Omnibus: 2026-08 single-file review sweep — 100 fixes across CLI, crawlers, setup, patch engine, self-update, and all vendor backends - #223
Conversation
The mode built GET /patch/package/<uuid> (proxy) or GET /v0/orgs/<slug>/patches/package/<uuid> (authenticated) — routes no deployed server has served since the on-demand package flow was dropped (depscan e2e test 80 pins their absence). Every package-mode fetch failed per-UUID and silently degraded to per-file blob downloads: slower, noisier, and never what the flag promised. Now `--download-mode package` / `SOCKET_DOWNLOAD_MODE=package` fails fast with a removal notice instead of degrading. The local `.socket/packages/` read path in the apply pipeline is intentionally untouched — pre-staged archives still apply. Removed: DownloadMode::Package, ArchiveKind (collapsed to diff-only fetch), ApiClient::fetch_package. Tests updated to pin the removal error and that repair in package mode hard-fails without touching the package archive route. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nfig read - fs.rs: atomic_write_bytes_as now flushes before sync_all, so tokio's buffered background-write errors (e.g. ENOSPC) fail the stage write instead of committing a truncated file over the target. - socket_cli_config.rs: read config.json via open_regular_file so a FIFO planted at the user-writable path cannot wedge every networked command. Part of the 2026-08-19/20 single-file review sweep (see PR body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tate/lock - channel.rs: launcher_cache_roots also probes ~/AppData/Local (the gem launcher's Windows fallback) so --update cannot clobber the launcher cache when LOCALAPPDATA is stripped. - state.rs: update-check.json read through a sync open_regular_file twin; a planted FIFO no longer wedges every command. - swap.rs: update.lock acquired with O_NONBLOCK + is_file guard; a FIFO there no longer wedges --update. Part of the 2026-08-19/20 single-file review sweep (see PR body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt, FIFO wedges
- maven_crawler.rs: <properties> added to skip_sections so a property named
version/groupId/artifactId can no longer win first-match extraction over
the project's real coordinates.
- composer_crawler.rs: containment boundary under --cwd . normalized to an
absolute root; install-path escapes are rejected instead of vacuously
contained by starts_with("").
- python_crawler.rs / ruby_crawler.rs: dist-info METADATA and
.bundle/config reads routed through open_regular_file; planted FIFOs no
longer wedge scan/apply.
- crawler e2e suites extended accordingly (incl. cwd restore-on-drop guard).
Part of the 2026-08-19/20 single-file review sweep (see PR body).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rrectness - package_json find.rs/update.rs, setup composer/pypi (detect, edit), gem version.rs: every raw manifest/lock read (pnpm-workspace.yaml, package.json, composer.json, pyproject.toml, requirements.txt, Gemfile.lock) routed through guarded regular-file reads; planted FIFOs fail fast instead of wedging setup/--remove. - setup/gem/mod.rs: six raw-read sites guarded; discover_bundler_project requires a regular file (matching bundler's File.file? gate); a read error on an existing .socket/.gitignore no longer clobbers it via unwrap_or_default. - setup/gem/update.rs: remove with the managed marker present but no matchable block now errors (exit 13) instead of deleting the plugin dir while the Gemfile directive stays live. Part of the 2026-08-19/20 single-file review sweep (see PR body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t fixes
- apply_lock.rs: the lock open moved inside the retry loop so a parked
waiter re-opens the current inode instead of flocking one repair
unlinked.
- rollback.rs: before-blob verify uses symlink_metadata + is_file so a
symlink/FIFO squatting blobs/<hash> cannot pass verify or wedge the read.
- redirect/golang_local.rs: reconcile drops the replace directive for
copies it prunes on version-bump skew (no more dangling directives
bricking go build); the hosted-owned-replace refusal is scoped to the
apply-managed GO_PATCHES_DIR so the vendor local-build leg is not
blocked.
- redirect/state.rs: ledger read guarded against non-regular files;
empty-uuid records no longer act as contains("") wildcard anchors.
- redirect/takeover.rs: pnpm claims match v6 resolved-peer and v5 suffixed
instance keys, ending silent half-takeovers.
Part of the 2026-08-19/20 single-file review sweep (see PR body).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…FIFO wedges - npm_lock.rs / yarn_classic_lock.rs / yarn_berry_lock.rs: wired revert now probes post-restore whether the lock (and for berry, package.json resolutions) still resolves through the uuid dir before deleting it — covers npm's shrinkwrap rename and hand-copied/re-keyed lock entries. - yarn_classic_lock.rs: duplicate-key lock blocks now refuse pre-flight with vendor_lock_entry_ambiguous instead of splicing the first block and leaving yarn's last-wins winner unpatched while reporting success. - npm_common.rs: object-form bundleDependencies honored (npm-bundled Object.keys ground truth) — no more fail-open on bundled deps. - bun_lock.rs: both bun.lock write sites preserve file mode. - berry_zip.rs: file/dir path collisions and trailing-slash Regular tar entries now fail closed instead of emitting a zip with a guessed checksum. - FIFO-wedge guards on all raw lock/config reads across the family (npm_flavor read_lock + in-use probe, npm select_lockfile + revert, yarn shared read_yarn_lock, .yarnrc.yml, classic revert). Part of the 2026-08-19/20 single-file review sweep (see PR body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…IFO wedges - pnpm_lock.rs + pnpm_lock_legacy.rs + state.rs: packages-snapshot wiring keys no longer embed the stale uuid, so cross-generation original-fill and revert work after a re-vendor under a new uuid; carry_forward keeps pnpm created-flags across uuid changes. - pnpm_lock.rs: workspace read treats only NotFound as "no file" (no more scaffold-overwrite of a user's pnpm-workspace.yaml on read errors); CRLF workspace/lock files refuse or count as drift instead of duplicating overrides:, silently converging, or failing open on the in-use probe; peer deps literally named version/deprecated survive edit_packages. - pnpm_lock_legacy.rs: gains the drift-keep lossiness gate on revert plus the CRLF in-use fix. - lock_inventory.rs (all 15 lockfile reads), mod.rs (migration-risk probe), common.rs (jar/nupkg in-sync probe, lock fragment splice), pnpm pair reads: FIFO-wedge guards throughout. - path.rs: eco-dir symlink lstat guard — the orphan sweep can no longer follow a planted .socket/vendor/<eco> symlink and delete through it. Part of the 2026-08-19/20 single-file review sweep (see PR body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ption, BOM/include parsing, FIFO wedges - pypi_wheel.rs: version component keeps PEP 440 + and ! (2.0.0+cu118 no longer becomes 2.0.0_cu118, which pip/uv reject as InvalidWheelFilename); five raw-read sites guarded. - toml_surgery.rs: replace_files_array only rewrites the real [package.files] array — deps/extras literally keyed "files" are no longer corrupted into duplicate wheel arrays; remove_exact_line/ remove_table_if_empty splice by byte span, preserving CRLF files. - pypi_requirements.rs: line-1 BOM stripped before pin matching (pip/uv ground truth) and pip's attached -rdev.txt include form recognized — no more duplicate transitive appends. - FIFO-wedge guards on every raw read across the family: flavor detection (pypi.rs), pdm/pipenv/poetry/uv load + revert, requirements collect + revert. Part of the 2026-08-19/20 single-file review sweep (see PR body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sk prune, FIFO wedges - gem.rs: unparseable-but-valid-Ruby gem declarations (gem"rack") refuse instead of falling through to a duplicate append; re-vendor over a pre-bare CHECKSUMS entry no longer warns of spurious drift; all 9 Gemfile/lock/stub read sites guarded. - cargo.rs: prune_empty_vendor_dirs skips NotFound husks left by unwind paths instead of aborting the prune; hosted-redirect residue read guarded. - cargo_config.rs / cargo_lock.rs / composer_lock.rs: every raw config/lock read guarded against non-regular files (scan probe, vendor pre-flight, wet apply/remove, revert). Part of the 2026-08-19/20 single-file review sweep (see PR body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…preservation, GOPROXY pipes, FIFO wedges - registry_fetch.rs: zip extraction bounds reads with take() — the zip crate does not bound an entry read by its declared size, so the MAX_ENTRY/MAX_TOTAL decompression caps were bypassable at 3 sites; GOPROXY now splits on | as well as , per the Go spec; berry foreign cacheKey refusal hoisted before any network I/O; pypi Unverifiable message garble fixed. - go_mod_edit.rs / go_sum_edit.rs: joins preserve the file's own EOL so CRLF go.mod/go.sum stop churning wholesale; go.sum upsert's already-applied check requires exactly the wanted lines and no conflicting stale sums. - golang.rs: pins vendor-takes-over-hosted on the local-build leg (the refusal scoping lives in redirect/golang_local.rs). - maven_repo.rs (6 sites) / nuget_feed.rs (6 sites): FIFO-wedge guards on every pom/jar/config/lock/nupkg raw read; self-closing <packageSources /> no longer harvests phantom keys that brick restore. Part of the 2026-08-19/20 single-file review sweep (see PR body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…usal scoping, GC locking - get.rs / repair.rs: download failures under --silent now reach stderr (errors-only, not errors-hidden) instead of a mute exit 1. - apply.rs / rollback.rs: the mismatch-blob top-up and the before-blob gate probe EVERY on-disk copy of a duplicated package, completing the #216 multi-copy campaign — no more wedged rollbacks or partial applies when copies diverge. - apply.rs: both yarn-PnP refusal gates are scoped to npm-in-local-scope (and manifest npm targeting), so --global runs, non-npm --ecosystems runs, and polyglot PnP repos stop being refused wholesale; the three RED pins for this are un-ignored. - fetch_stage.rs: online fetch failures re-run the offline coverage filter against staged dirs — a patch fully satisfied by a local .socket/packages archive no longer goes Unavailable; the pre-existing RED e2e pin is un-ignored and green. - remove.rs: corrupt manifests report manifest_invalid per CLI_CONTRACT (not manifest_unreadable); the detached-cancel message respects --silent. - scan/gc.rs: wet GC takes the apply lock (non-blocking; contention skips the pass) before its manifest read-modify-write and orphan sweep. Part of the 2026-08-19/20 single-file review sweep (see PR body). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Known residuals & follow-ups surfaced by the post-assembly re-reviewNone of these block this PR; they are recorded here so they are not lost.
|
…fest consumer read_manifest used a bare tokio read_to_string, so a FIFO planted at the manifest path blocked open(2) forever — list/apply/remove/repair all hung with zero output, and apply runs from install hooks, so this wedged `npm install` indefinitely. Now opens via open_regular_file: non-regular files fail fast with InvalidInput (labeled manifest_unreadable by the CLI), missing files keep mapping to Ok(None). Found live by this PR's post-assembly multi-ecosystem usability probe of the FIFO-wedge class; RED-verified (the new regression test wedged its full 5s deadline against the unfixed code, passes in ms after). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Multi-ecosystem smoke test results (post-assembly)Run locally against the assembled branch (docker daemon, Docker e2e suites — 11/11 green
Every suite drives the real CLI through a real package-manager install → patch-applied → (where applicable) revert chain. Hands-on usability probe (built binary, planted FIFOs)The sweep's headline class is the FIFO-wedge; the probe planted FIFOs where the CLI expects regular files and drove the real binary:
Residual found by the probe → fixed as the 100th fix (49508d5)An extension probe of the same class found that a FIFO squatting 🤖 Generated with Claude Code |
…M_UUIDS Production extended pkg:gem/activestorage@6.0.3 with a second advisory's patch on 2026-08-19T21:19Z (GHSA-w749-p3v6-hccq / CVE-2022-21831, uuid 6c4141c5-1535-4fd2-9db1-b5f8e4834bdb) and the server-ranked hosted selection now wires it, failing the pinned any-of assert exactly as designed. The new patch was live-verified before extending the pin: the served .gem matches the /info checksum, carries the Socket patch header in image_processing_transformer.rb, and every other file is byte-identical to stock rubygems 6.0.3. Both advisories' uuids stay in the set (the batch API still publishes both). Verified live: preflight_required_patches_are_published + gem_bundler_hosted_install_proof green against production. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
hosted-e2e failure on ee6488c — root-caused and fixed in f842a21 (not a sweep regression)
Per the assert's own instruction, the new patch was verified before extending the pin:
Re-verified live against production after the pin extension: 🤖 Generated with Claude Code |
…lass-split fallback-home failure semantics Review-round fixes for the two verified Bugbot findings on #222. Finding 1 (Medium): the containment guard's `gem_bundle_config_path_ignored` was a bare eprintln inside the crawler — it never reached any --json `warnings[]` and printed under --silent, violating the repo-wide warning conventions (#219/#220 + the #223 omnibus silent fixes). The crawler is now print-free: the refusal is RECORDED on `BundleStoreDiscovery.skipped_config_path`, and a shared `config_path_ignored_warning(value)` builder feeds the CLI channels — scan pushes it onto the same run-level channel as the PnP layout refusals (JSON `warnings[]` on both the zero-package and >=1-package envelopes; one stderr line gated on !json && !silent), and apply carries it in its Envelope `warnings[]` plus one gated stderr line. Scoped like the crawl that hit it: local mode, gem in --ecosystems/manifest scope. Finding 2 (High, with nuance): with an env/config bundle root, get_gem_paths appends the gem-env fallback homes and the multi-copy fan-out patched EVERY copy with per-copy loud-fail — so a gem present in both the bundle store and a shared home (rvm @global, root-owned system dir) failed the WHOLE run on a permission failure or variant mismatch THERE, even though the copy bundler loads patched fine. Patching a shared home's copy is not itself wrong (plain apply always patched GEM_HOME when no store existed); the defect was failure semantics crossing store classes. Fix: discovery's store list is exposed (`RubyCrawler::discover_bundle_stores`, fs-probes only) and apply's gem fan-out classes each copy — bundle-path store copies stay PRIMARY (loud-fail, unchanged); gem-env fallback-home copies become BEST-EFFORT once at least one store copy applied: a variant mismatch or write failure there is a per-copy non-fatal Skipped event (`gem_fallback_home_skipped`, detail names the path and reason; gated stderr twin), never a run failure. Parity edge kept: with NO bundle-store copy (the historic fallback-only layout, and every --global run) the home copy IS primary and keeps loud-fail exactly as pre-#218 apply. TDD evidence (red -> green on the rebased tip): scan/apply --json missing the warnings[] entry and the --silent leak (3 tests, in_process_gem_config_warning.rs); mismatched-home-copy exit 1 and Failed-event-on-strict-refusal (in_process_gem_fallback_home.rs), with fallback-only loud parity and both-copies-patched pinned green throughout. CLI_CONTRACT.md documents the copy classes and the warning channels. Gates: touched files rustfmt-clean; cargo clippy --workspace --all-features -D warnings clean; core --lib 2535, cli --lib 436; crawlers::ruby 50, crawler_ruby_e2e 25; gem+npm multicopy 2+2; apply_network 11, apply_invariants 4, cli_gem_variant_mismatch_policy 6, cli_apply_silent 2, e2e_gem hermetic 8 (6 shown +cache selftests), e2e_scan, cli_scan_silent, docker_e2e_gem — all green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Omnibus: 2026-08-19/20 single-file review sweep — 99 fixes across CLI, crawlers, setup, patch engine, self-update, and all vendor backends
This PR lands the complete 2026-08-19/20 single-file line-by-line review sweep: ~50 production files reviewed, 100 distinct fixes, every one pinned by a regression test that was verified RED against the pre-fix code. It also carries the branch's original commit (dead
--download-mode packageremoval, previously the sole content of this PR).How to review this
The diff is large but highly regular: 42 of the 100 fixes are the same one-line-shape fix (the FIFO-wedge guard, below) applied to every raw filesystem read in the codebase, and each fix comes with a focused regression test named after the failure. The per-file map below links every fix to its test. Suggested order: read
utils/fs.rsfirst (the shared guard helpers), then review by section.Dominant bug classes
fs::read/read_to_stringof a workspace-controlled path (lockfiles, manifests, configs) blocks forever inopen(2)if that path is a FIFO, wedgingscan/apply/vendor/setupand in two cases every CLI command (update-check.json, socket-cliconfig.json). Fixed via the sharedopen_regular_fileguard (O_NONBLOCK+is_fileon unix, metadata gate elsewhere): reads either refuse fast with a real error or degrade to the documented "missing/malformed" contract, per call site.vendor_lock_entry_ambiguousrefusal); poetryfiles-keyed sub-tables corrupted by the shared TOML array rewriter; pnpm peer deps literally namedversion/deprecateddropped from the lock; wheel version escaping mangled+/!into names pip/uv reject; and more.--silentfailures (4) —get/repair/apply-path download failures exited 1 with zero output on both streams;--silentnow means errors-only, not errors-hidden.repairhad unlinked.<properties>coordinate leak, launcher AppData fallback miss, zip declared-size cap bypass (3 sites), GOPROXY|fallback splitting, gem remove deleting the plugin dir with the directive still live, eco-dir symlink sweep escape, empty-uuid redirect anchor matching everything, pnpm suffixed-instance takeover misses, BOM-blind requirements.txt parsing, and dangling go.modreplacedirectives after version-bump skew.Provenance & verification
O_NONBLOCKFIFO writer-release in four vendor test timeout branches).Testing
cargo test --workspace --no-fail-fast→ 5036 passed, 0 failed across 179 test binaries (ignored = opt-in docker/setup-matrix suites and RED pins for known-open bugs tracked separately).manifest/operations.rs— a FIFO at.socket/manifest.jsonhunglist/apply, i.e.npm installvia the install hook), fixed in this PR as the 100th fix.Fix map (by area)
Manifest engine (found by this PR's smoke pass)
crates/socket-patch-core/src/manifest/operations.rs—read_manifestraw-read the manifest, so a FIFO squatting.socket/manifest.jsonwedgedlist/apply/remove/repairforever inopen(2)→ now opens viaopen_regular_file, failing fast withInvalidInput/manifest_unreadablewhile NotFound keeps mapping toOk(None)(apply runs from install hooks, so this hungnpm installwith zero output; tested bytest_read_manifest_fifo_fails_fast_instead_of_wedging, RED-verified)CLI commands (apply/get/remove/repair/rollback/fetch_stage/scan-gc) — 9 fixes
crates/socket-patch-cli/src/commands/apply.rs— two fixes:mismatch_blob_gapsnow takesVec<PathBuf>per PURL and probes every physical copy, breaking per file once the hash is queued (a clean root copy no longer masks a drifted nested npm duplicate, which previously failed to apply with exit 1; tested bymismatch_blob_gaps_probes_every_copyin-module andmismatch_blob_topup_probes_every_copy_of_a_duplicated_packageintests/apply_network.rs).npm_in_local_scope()(local mode + npm passes the exactcli_namefilterpartition_purlsuses) and the with-manifest gate additionally requiresmanifest_targets_npm()(--global/--global-prefixruns, non-npm--ecosystemsruns, and polyglot PnP repos with no npm patches were refused wholesale and patched nothing; tested by the 3 un-ignored RED tests intests/e2e_safety_yarn_pnp.rs, with the live no-manifest and npm-in-scope refusal counter-guards unchanged).crates/socket-patch-cli/src/commands/fetch_stage.rs— the post-download failure gate used aggregate fetch counters → the offline gate's per-patch coverage rule is factored intopatches_without_source()and re-run against the staged dirs on any failure, bailing only when some patch is truly uncovered (a patch covered by a local.socket/packages/<uuid>.tar.gzor diff archive wasUnavailableonline whenever downloads failed — online strictly worse than--offlineon the same cache; tested by 3 new in-module tests incl. the no-local-sourceUnavailableovershoot guard).crates/socket-patch-cli/src/commands/get.rs—--silentswallowed the download loops' error output (8[fail]/[error]sites gated on!json && !silent) → error prints now gate on!jsononly, per the--silent= "errors only" contract (get <purl> --silentwith a failed patch fetch exited 1 with zero output anywhere; tested bytests/cli_get_silent_errors.rs— NEW file, wiremock search-200 + view-500, with loud control and informational-stays-suppressed guard).crates/socket-patch-cli/src/commands/remove.rs— two fixes:manifest_unreadable→ErrorKind::InvalidData(exists-but-unparseable) now emits the contract'smanifest_invalid, mirroringlist(JSON consumers saw corrupt manifests as transient I/O failures; tested by the flipped pin plus schema-invalid and directory-as-manifest cases intests/remove_invariants.rs).--silent→ now gated on!json && !silentlike the manifest path (interactive decline under--silentprinted suppressed-class chatter; tested byremove_detached_interactive_n_cancel_message_respects_silent(PTY + loud control) intests/interactive_prompts_e2e.rs).crates/socket-patch-cli/src/commands/repair.rs—repair --silentwith failed downloads exited 1 completely mute → a failures-only reprint (FetchMissingBlobsResultwith zeroed success counters throughformat_fetch_result) now reaches stderr;--jsonkeeps carrying the failure in the envelope and loud-mode stdout is untouched (tested byrepair_silent_still_reports_download_failures_on_stderrintests/repair_invariants.rs).crates/socket-patch-cli/src/commands/rollback.rs— the before-blob gate verified only the representative first copy → it now probes every copy inall_packages_multi, breaking per file on the firstMissingBlob; exact twin of apply'smismatch_blob_gapsfix (an online rollback of a diverged npm duplicate skipped the on-demand before-blob download and wedged mid-run withMissingBlob, leaving the nested copy patched on every retry; tested bytests/rollback_multicopy_blob_gate.rs— NEW file: blob-GET + exit-0 + restore assertions plus an all-copies-original offline anti-overshoot guard).crates/socket-patch-cli/src/commands/scan/gc.rs— wet GC pruned the manifest and swept blobs with no apply lock →run_apply_gcnow takes a non-blockingapply_lock::acquire(after the self-locking vendored half), and contention skips the pass without failing the scan (a concurrentget/applymid-write could have its new manifest entry clobbered and its fresh blobs deleted; tested byrun_apply_gc_skips_prune_and_sweep_while_apply_lock_is_heldin-module).Reviewer note:
tests/apply_network.rs:916still#[ignore]sapply_online_uses_cached_package_archive_when_downloads_failwith a now-stale reason — the fetch_stage.rs change in this PR is exactly the fix it pins; recommend un-ignoring it in this PR after a green run.Crawlers (composer/maven/python/ruby) — 4 fixes
crates/socket-patch-core/src/crawlers/maven_crawler.rs—<properties>was not inparse_pom_group_artifact_version'sskip_sections, so a schema-valid property literally namedversion/groupId/artifactId(free-formxs:any; Maven warns but permits it) won first-match extraction over the project's own coordinates → now the whole<properties>block is depth-skipped. Unlike the parser'sNonefailures this produced a wrongSomethat suppressed the directory-path fallback, so scan emitted a bogus purl — a silent patch miss or worst-case a wrong-version patch applied in place. (tested bytest_parse_pom_property_named_coordinate_does_not_leak, in-module)crates/socket-patch-core/src/crawlers/python_crawler.rs—parse_metadata_headersplain-opened<pkg>.dist-info/METADATAviatokio::fs::read_to_string; a planted FIFO there blocksopen(2)forever, wedgingscan(crawl_all) andapply(find_by_purls) with no error or timeout → now opens throughutils::fs::open_regular_file(O_NONBLOCK+ fstatis_file), and a rejected METADATA degrades into the dir-name fallback so the package stays discoverable. (one malicious build-hook FIFO no longer hangs the whole crawl; tested byread_python_metadata_rejects_fifo_metadata_without_hangingintests/crawler_python_e2e.rs)crates/socket-patch-core/src/crawlers/ruby_crawler.rs—app_config_bundle_pathplain-opened.bundle/config($BUNDLE_APP_CONFIG/config) — theopen_regular_filetwin fix(gem): discover flat BUNDLE_PATH gem layouts — bundler-1 env-var installs were invisible to scan/get/apply #218 introduced after the composer fix → same non-blocking guard; a rejected config only drops the app-config root while the unconditionalvendor/bundleprobe still runs. (a FIFO.bundle/configwedged scan/get/apply forever for ANY project with a Bundler manifest; tested byapp_config_fifo_does_not_wedge_discovery, in-module)crates/socket-patch-core/src/crawlers/composer_crawler.rs— under the CLI default--cwd .,resolve_project_rootnormalizes the containment boundary to the EMPTY path, andstarts_with("")holds for every path — so the install-path escape gate was vacuous in exactly the default invocation and a tampered installed.json absoluteinstall-pathresolved out of tree as a patch WRITE target →resolve_install_pathnow rejects any anchored/unanchored mismatch between the resolved path and the boundary (is_anchoredcoversRootDirand Windows drive-relativeC:evilprefixes); strictly narrowing, absolute-root behavior unchanged. Note: a legitimately-absolute in-project install-path is now also refused under a relative cwd — fail-closed by design, consistent with the existing no-fallback rule. (tested bytest_resolve_install_path_rejects_absolute_escape_from_relative_root, in-module, and end-to-end byinstall_path_absolute_escape_rejected_under_relative_cwdintests/crawler_composer_e2e.rs, which drives the realget_vendor_paths→crawl_all/find_by_purlschain from a relative cwd with the escape target existing on disk)crates/socket-patch-core/tests/crawler_npm_e2e.rs— rustfmt reflow of twoassert_eq!calls only; no behavior change.Setup writers + package.json discovery (composer/gem/pypi/npm-family) — 10 fixes
crates/socket-patch-core/src/package_json/find.rs— workspace discovery readpnpm-workspace.yamland every glob-discovered memberpackage.jsonwith a plainread_to_string, so one planted FIFO wedgedsetupdiscovery forever; both sites now go through a newread_project_file_to_string(O_NONBLOCKopen_regular_file+ regular-file gate), falling back cleanly (FIFOpnpm-workspace.yamldegrades to the package.jsonworkspacesfield) (silent infinite hang → fast error; tested bypnpm_workspace_fifo_does_not_wedge_discovery,member_package_json_fifo_does_not_wedge_discovery)crates/socket-patch-core/src/package_json/update.rs—update_package_json/remove_package_jsonopened a discovery-listed FIFOpackage.jsonand blocked forever; both reads now use find.rs's shared guarded reader and surfaceErrorstatus instead (wedgedsetupANDsetup --remove→ clean per-file error; tested bytest_update_fifo_package_json_does_not_wedge,test_remove_fifo_package_json_does_not_wedge)crates/socket-patch-core/src/setup/composer/mod.rs—edit()(add/remove hook) readcomposer.jsonraw; a FIFO there wedgedsetup/--removein PHP projects. Now guarded viaread_composer_json_to_string; the missing-file-on-remove no-op contract is preserved (NotFound still returnsOk(false)) (infinite hang →Errorstatus; tested bytest_add_fifo_composer_json_does_not_wedge,test_remove_fifo_composer_json_does_not_wedge)crates/socket-patch-core/src/setup/gem/mod.rs— three fixes:needs_write,is_generated,stamp_ignore_missing,add_stamp_gitignore,remove_stamp_artifacts, bundler plugin-index read) viaread_regular_to_string, andwrite_fileswitched toatomic_write_bytes_preserving_modeso the wedge can't just move to the write — rename-over never opens the destination, replacing a squatting FIFO instead of blocking on it (setup/--check/--remove hung forever → fail fast or sync over; tested bytest_check_fifo_plugins_rb_does_not_wedge,test_add_fifo_plugins_rb_does_not_wedge_and_replaces_it,test_remove_fifo_plugins_rb_does_not_wedge,test_remove_registration_fifo_index_reports_residue_not_wedge)discover_bundler_projectaccepted any path whose metadata stat'd; now gates onis_file()like bundler's ownFile.file?, so a directory/FIFO namedGemfileis skipped in favor of the ancestor manifest bundle actually loads (setup errored wherebundle installworks → matches bundler; tested bytest_discover_skips_directory_named_gemfile,test_discover_skips_fifo_named_gemfile)add_stamp_gitignoretreated any read error on an existing.socket/.gitignoreas "empty" and rewrote it, destroying user lines (non-UTF-8 gitignore bytes are legal); now only NotFound defaults to empty, other errors fail setup with the file untouched (silent user-data loss → hard error; tested bytest_add_never_clobbers_unreadable_stamp_gitignore,test_add_fifo_stamp_gitignore_errors_not_wedges)crates/socket-patch-core/src/setup/gem/update.rs—--removeon a Gemfile whose managed block was mutated (e.g. stripped final newline) reported "not configured" and deleted.socket/bundler-pluginwhile the liveplugin ... path:directive stayed in the Gemfile, breaking every laterbundle install(exit 13); the marker-present-but-unmatchable case is now anErrornaming the manual remedy, and the orchestrator's Error early-return keeps the plugin files (broken installs → safe refusal with remedy; tested bytest_remove_keeps_plugin_files_when_managed_block_is_unmatchable)crates/socket-patch-core/src/setup/gem/version.rs—probe_bundlerreadGemfile.lock/gems.lockedraw; a FIFO at the lock path wedged setup/--check and every Gemfile-block update inside the version probe; now routed through the parent module's guarded reader, failing fast to the time-boundedbundle --versionfallback (infinite hang → bounded fallback probe; tested bytest_probe_fifo_lockfile_does_not_wedge)crates/socket-patch-core/src/setup/pypi/detect.rs—detect_python_pmreadpyproject.tomlraw behind a metadata-only project gate; a FIFO there (no lockfile present) wedged setup/--check and the configured-ecosystems probe; now guarded, degrading to thePipfallback (infinite hang → Pip fallback; tested bytest_detect_fifo_pyproject_does_not_wedge)crates/socket-patch-core/src/setup/pypi/edit.rs—add_hook_dependency/remove_hook_dependencyread the manifest raw, and detection never opens the file it hands the edit path, so a FIFO atrequirements.txt/pyproject.tomlwedgedsetup/setup --remove; now guarded, failing fast toPthStatus::Errorwhile preserving the NotFound contracts (add+Requirements creates, remove no-ops) and never replacing the squatting FIFO (infinite hang → clean error, FIFO untouched; tested bytest_edit_fifo_manifest_does_not_wedge)Reviewer notes: the CLI-side raw manifest reads in
crates/socket-patch-cli/src/commands/setup.rs(composer/gem/pypi--check/detect paths and state probes) are known unfixed twins, deliberately out of this slice; the plain Gemfile reads remaining insetup/gem/update.rsare deliberate — the new discoveryis_filegate closes their production route, leaving race-only exposure.Patch engine core (apply_lock, rollback, redirect state/takeover/golang_local) — 7 fixes
crates/socket-patch-core/src/patch/apply_lock.rs—acquireopenedapply.lockonce before the retry loop and re-flocked that stale handle for the whole--lock-timeoutbudget → the open now lives inside the loop, binding every attempt to whatever inode the path names now (a waiter parked acrossrepair's sanctioned unlink could flock the orphaned pre-deletion inode and hand out a second live "exclusive" guard — concurrent manifest/package corruption; tested bywaiter_does_not_lock_orphaned_inode_after_lock_file_deleted, previously RED#[ignore], now enabled)crates/socket-patch-core/src/patch/rollback.rs— before-blob probe/read were entry-type blind (metadatafollows symlinks, baretokio::fs::read) → verify now lstats the blobs entry and refuses non-regular files asMissingBlob("not a regular file",target_hashkept so re-download self-heals), with a defense-in-depth lstat twin at the read site (a hex-named symlink committed atblobs/<hash>passed the string guard and leaked an out-of-tree file's existence + content hash via the "Got:" mismatch error; a FIFO hung rollback forever; tested bytest_verify_file_rollback_rejects_symlinked_blob_entry,test_rollback_package_patch_symlinked_blob_entry_blocked,test_verify_file_rollback_rejects_fifo_blob_entry)crates/socket-patch-core/src/patch/redirect/golang_local.rs— two fixes:reconcile_go_redirectsloop (b) pruned copy dirs by exact purl while loop (a) kept the directive whenever the module was desired at another version → (b) now drops the GoPatches-owned directive first iff its path exactly equals the pruned copy'sreplace_target_path(path-exact, dry-run-aware), so a not-yet-applied version bump no longer leaves a danglingreplacethat bricks everygo buildwith "replacement directory does not exist" (tested bytest_reconcile_version_bump_never_leaves_dangling_directive+ over-drop guardtest_reconcile_prunes_stale_copy_keeps_repointed_directive)base_rel == GO_PATCHES_DIR(apply's sole production copy base), restoring the documented vendor-takes-over-hosted policy;vendor --vendor-source=local/offline/auto-fallback can now vendor hosted-scanned Go modules (fix owned by the vendor/golang.rs pass; tested byvendor::golang::tests::test_local_vendor_takes_over_hosted_replaceand the new apply-side pintest_apply_refuses_hosted_owned_replace)crates/socket-patch-core/src/patch/redirect/state.rs— two fixes:load_redirect_stateread the untrusted ledger with baretokio::fs::read→ newread_ledger_bytesopens viaopen_regular_file(O_NONBLOCK + fstat on unix; plain open +is_fileelsewhere), so a planted FIFO fails loudly asCorruptRedirectStateinstead of wedging every ledger-consulting flow (scan, vex, list, vendor) forever;NotFound → Ok(None)unchanged, symlink-to-regular-file still loads (tested byload_fifo_ledger_fails_fast_instead_of_wedging)drop_superseded_purlused record uuids as artifact anchors without filtering empties, so a"uuid": ""record (hand-repaired ledger — a workflow the corrupt-ledger message itself instructs) madecontains("")claim every package's edits → empty uuids are filtered and the claim falls back to version-exact-only, so dropping one purl no longer destroys other packages' only revert data (tested bydrop_superseded_purl_empty_uuid_record_claims_no_anchored_edits)crates/socket-patch-core/src/patch/redirect/takeover.rs— the pnpm takeover claim matched edits bykey == lock_keyexactly, missing v6 resolved-peer (name@ver(peer@x)) and v5 suffixed (name@ver_suffix) instance keys → pnpm claims (kindredirect_pnpm_resolutiononly) also accept a(/_peer boundary after the exact version (never-/./alnum, which would swallow a prerelease sibling), so takeover no longer "succeeds" as a silent half revert that strands peered edits in the ledger and leaves the expiring hosted tarball wired for every dependent resolving through the peered instance (tested bynpm_pnpm_v6_peered_instance_takeover_reverts_every_instance,npm_pnpm_v5_suffixed_instance_takeover_reverts_every_instance, boundary guardnpm_pnpm_prerelease_sibling_peered_edit_is_not_claimed)crates/socket-patch-core/src/patch/redirect/mod.rs— no behavior change: rustfmt-only churn in two test assertions (file was reviewed clean; safe to skim)Self-update + shared utils (channel/state/swap, fs, socket_cli_config) — 5 fixes
crates/socket-patch-core/src/utils/fs.rs—atomic_write_bytes_asswallowed stage-write failures: tokio'swrite_allonly buffers (up to 2 MiB) and itssync_allstores the background-write error back into the handle instead of returning it, so ENOSPC/EIO/EFBIG during the stage write returnedOk(())and renamed the truncated stage over the intact destination. Now an explicitfile.flush().awaitbetweenwrite_allandsync_allsurfaces the error and the stage is cleaned up (silent data loss on every user-owned file we edit — package.json, go.mod, lockfiles, vendor/state.json — via both writer variants; tested byatomic_write_failed_stage_write_errors_and_keeps_target, deterministic via anRLIMIT_FSIZEcap). Context for the FIFO fixes below: this file also hosts the sweep's shared asyncopen_regular_filehelper (pre-existing) — O_NONBLOCK open on unix so a FIFO can't wedgeopen(2), with fstat taken from the open handle so size/bytes can't come from different inodes, rejecting FIFOs/devices/dirs withInvalidInput.crates/socket-patch-core/src/update/state.rs—load_state()used barestd::fs::read, so a FIFO planted at<state_dir>/update-check.jsonwedged the open forever → now reads via sync helperread_state_bytes(O_NONBLOCK + handle-basedis_file) and degrades to never-checked like any other unreadable state (one special file in the cache dir hung every CLI command with no output, since the passive update notifier loads this synchronously at command start; tested byload_state_fifo_state_file_degrades_instead_of_wedging).crates/socket-patch-core/src/update/swap.rs—acquire_update_lock()openedupdate.lockwith a plain blockingO_WRONLYopen, so a FIFO planted there wedged--updateforever before it did anything → now opens withO_NONBLOCKon unix (FIFO-no-reader → immediate ENXIO →SwapFailed) plus an unconditional handle-basedis_filecheck giving a clear "not a regular file; remove it and retry" error. The fd is only ever flock(2)ed, so O_NONBLOCK is a no-op for the normal regular-file case (tested byupdate_lock_fifo_lock_file_errors_instead_of_wedging; regular path re-pinned by the pre-existingupdate_lock_is_exclusive_and_released_on_drop).crates/socket-patch-core/src/utils/socket_cli_config.rs—read_from_diskused rawstd::fs::readon the user-writable socket-cli config path, so a FIFO planted at<data dir>/socket/settings/config.jsonwedged every networked command during API-client construction → now reads via privateread_regular_file(same O_NONBLOCK +is_fileguard); a non-regular file warns and is treated as absent per the existing warn-and-stop probe contract (tested byfifo_config_file_is_ignored_instead_of_wedging).crates/socket-patch-core/src/update/channel.rs—launcher_cache_rootsmissed the gem launcher's Windows fallback cache root~/AppData/Local(launcher.rb:ENV[\"LOCALAPPDATA\"] || File.join(Dir.home, \"AppData\", \"Local\")), so with LOCALAPPDATA stripped a launcher-cached binary misdetected as Standalone and--updateswapped the per-version SHA-verified cache entry in place, desyncing the launcher's version pin → the root is now probed unconditionally, keeping the pure-function/table-testable design (worst case on Unix is a contrived-layout refusal with the gem upgrade hint; tested bylauncher_cache_detected_via_home_appdata_fallback, forward-slash spelling so it runs on Unix runners).Vendor: npm family (npm/bun/yarn classic/yarn berry/berry_zip) — 14 fixes
crates/socket-patch-core/src/vendor/npm_common.rs—declares_bundled_depsignored the OBJECT formbundleDependencies: {"dep": "…"}, which npm honors (Object.keys(bd)for any truthy non-array) → newValue::Object(o) => !o.is_empty()arm makes the refusal fire (a bundled-deps package previously vendored "successfully" with its bundlednode_modulespruned, breaking every install; tested bydeclares_bundled_deps_matches_npm_value_shapes+bundled_deps_refusal_covers_object_formin npm_lock.rs)crates/socket-patch-core/src/vendor/npm_flavor.rs— FIFO-wedge on the flavor/in-use probes:read_lockandlock_text_mentions_uuidraw-read lockfiles, so a planted FIFO wedged flavor detection (every npmvendor) and the GC in-use probe forever inopen(2)→ both now read via a module-localread_regular_to_stringoveropen_regular_file(O_NONBLOCK + fstat is_file; plain-open fallback on non-unix; symlinks still followed). Probe refusesvendor_lockfile_missingnaming the file; in-use probe returnsNone= fail-safe keep (tested byfifo_lockfiles_fail_fast_instead_of_wedging, unix-only)crates/socket-patch-core/src/vendor/npm_lock.rs— three fixes:select_lockfileraw-read the npm locks — the FIRST open on the vendor path (flavor detection is existence-only for npm locks), so a FIFO wedged every npm vendor → nowread_regular; NotFound-continue unchanged (tested byfifo_lockfile_fails_fast_instead_of_wedging_vendor_and_revert)revert_npm's wired-lock read had the same wedge → same fix; FIFO → InvalidInput → existingRevertOutcome::failed, artifact survives (same test, revert half)npm shrinkwraprenames package-lock.json → npm-shrinkwrap.json carrying thefile:entries, so the wired revert deleted the artifact while the surviving lock still resolved through it, failing every later install with ENOENT → new post-restore, pre-delete probe vialock_text_mentions_uuid(&[SHRINKWRAP, PACKAGE_LOCK])refuses with new codevendor_lock_still_wired_revert_blocked, artifact kept (tested byrevert_refuses_when_a_renamed_lock_still_resolves_through_the_artifact)crates/socket-patch-core/src/vendor/bun_lock.rs— bothbun.lockwrite sites (vendor rewrite + revert restore) used plainatomic_write_bytes, resetting a 0600/0640 lock to umask 0644 → swapped toatomic_write_bytes_preserving_mode(the last vendor backend on the plain writer; tested bylock_writes_preserve_file_mode, unix-only, covers both sites). Known follow-up: bun's wired revert still lacks the post-restore in-use probe its npm/yarn siblings gained in this sweep.crates/socket-patch-core/src/vendor/yarn_berry_lock.rs— three fixes:vendor_lock_still_wired_revert_blocked(tested byrevert_refuses_when_an_unrecorded_lock_entry_still_resolves_through_the_artifact+revert_refuses_when_a_rekeyed_resolutions_entry_still_references_the_artifact)file:spec → loopsguard_unwired_textual_revertper-file over [yarn.lock, package.json] (the helper's multi-name semantics are precedence, not OR; tested byempty_wiring_revert_refuses_while_resolutions_still_references_the_artifact)read_yarn_lock(lives in yarn_classic_lock.rs; classic vendor benefits),.yarnrc.yml, project package.json; revert: yarn.lock + package.json → all read via newpub(super) read_regular/read_regular_to_stringin yarn_classic_lock.rs; every arm maps through pre-existing NotFound/else handling (tested byfifo_files_fail_fast_instead_of_wedging_vendor_and_revert, unix-only, 5 scenarios)crates/socket-patch-core/src/vendor/yarn_classic_lock.rs— three fixes (plus hosting the shared guarded-read helpers above):vendor_lock_still_wired_revert_blocked(tested byrevert_refuses_when_an_unrecorded_lock_entry_still_resolves_through_the_artifact, incl. heal-then-converge re-run)read_regular_to_string; FIFO → InvalidInput → existingRevertOutcome::failed, artifact survives (tested byfifo_lock_fails_fast_instead_of_wedging_revert, unix-only)vendor_lock_entry_ambiguous("runyarn installto re-lock"; tested byduplicate_key_blocks_are_refused_before_any_write, both dup flavors, asserts nothing written)crates/socket-patch-core/src/vendor/berry_zip.rs— two fail-closed contract holes (neither could commit a wrong checksum — verify paths compare against the lock value — but both mislabeled rejections as "checksum mismatch" instead of "cannot rebuild deterministically"):seen_files/seen_dirsnever cross-checked, sopackage/aas both file and directory emitted a zip yarn's zipfs cannot hold →mkdirpnow Errs on a dir stem that is a known file, and the file branch mirrors viaseen_dirs(tested byfile_and_directory_path_collisions_fail_closed, all three orderings)/(pre-POSIX dir marker; GNU tar/node-tar read it as a directory) was silently hashed as a file → refused with "directory-style name" (tested byregular_entry_with_trailing_slash_fails_closed)Reviewer note: the new
vendor_lock_still_wired_revert_blockedrefusals cannot appear in dry-run previews (the probe runs after the real restore; a pre-restore probe would false-positive on every healthy revert) — accepted inaccuracy, wet runs are safe and converge on re-run.Vendor: pnpm + shared vendor infrastructure (state/inventory/common/path/mod) — 17 fixes
crates/socket-patch-core/src/vendor/pnpm_lock.rs— six fixes plus half of a cross-file invariant:revert_workspace,pnpm_entry_in_use) now go through newpub(super)read_regular/read_regular_stringoveropen_regular_file— a planted FIFO fails fast instead of wedging vendor/revert forever (tested byfifo_pair_files_fail_fast_instead_of_wedging_vendor_and_revert).pnpm-workspace.yamlno longer reads as "no file" (.ok()), which routed into the create path and overwrote the user's workspace definition with the root-only scaffold; now only NotFound → None, anything else refusesvendor_lockfile_missing(tested byunreadable_workspace_file_refuses_instead_of_scaffold_overwrite).vendor_lockfile_crlf_unsupportedinstead of appending a duplicateoverrides:section that pnpm rejects as a duplicated mapping key, bricking the project (tested bycrlf_workspace_file_refuses_instead_of_duplicating_overrides). Note: an LF-append to a CRLF file with no existing section used to succeed benignly and now also refuses — deliberate tightening.crlf_workspace_revert_is_a_drift_keep_not_silent_convergence).pnpm_entry_in_use: returnsNone(undeterminable) instead ofSome(false)("provably orphaned"), so the unwired-revert guard refuses instead of deleting a still-referenced artifact (tested bycrlf_lock_is_undeterminable_for_in_use_and_unwired_revert_refuses).edit_packagespeer-line drop: exact 4-space" version:"/" deprecated:"match replacestrim_start().starts_with, so 6-indent peer-dep lines for the real npm packagesversion/deprecatedsurvive the rewrite verbatim (tested bypackages_rewrite_keeps_peer_deps_named_version_or_deprecated).pnpm_lock_legacy.rs+state.rs):edit_packages/edit_snapshot_rekeyalways record the registry key instead ofblock.key, whosefile:form embeds the stale uuid on a new-uuid re-vendor and brokecarry_forward_wiring's file+kind+key original-fill — revert then warned "no recorded pre-vendor original" forever. Safe becauserevert_blocklocates the live block viarec.new's embedded key, never viakey(tested bynew_uuid_revendor_revert_removes_created_workspace_and_tables).crates/socket-patch-core/src/vendor/pnpm_lock_legacy.rs— three fixes plus the legacy half of the key invariant:fifo_pair_files_fail_fast_instead_of_wedging_vendor_and_revert).pnpm_legacy_entry_in_usereturnsNonefor a CRLF lock —sniff_lock_grammartolerates\rbut the LF-exact section probes miss, so it previously read as provably orphaned and the guard deleted a still-referenced tarball (tested bycrlf_lock_is_undeterminable_for_in_use_and_unwired_revert_refuses).revert_pnpm_legacynow gatesremove_treeondrift_skipped()→keep_artifactlike the v9/npm/bun backends, instead of deleting the tarball a drifted lock still resolves through — every later install ENOENT'd (tested bydrifted_lock_revert_keeps_the_artifact).edit_packagesrecords the registry key (same invariant as above; tested bynew_uuid_revendor_revert_restores_the_packages_block).crates/socket-patch-core/src/vendor/state.rs— two fixes plus the consumer side of the key invariant:load_state: ledger read via newread_state_bytesoveropen_regular_file; NotFound → empty-ledger arm unchanged. Previously a FIFO at.socket/vendor/state.jsonwedged every vendor-adjacent command (tested byload_fifo_state_fails_fast_instead_of_wedging).carry_forward_wiringpnpm created-flags OR-merge moved above theprev.uuid != entry.uuidearly return: "vendor created this table/file/section" is generation-independent, so a new-uuid re-vendor no longer makes--revertleave the vendor-createdpnpm-workspace.yamland an empty"pnpm":{"overrides":{}}husk behind. The wiring UNION stays same-uuid-scoped (tested bycarry_forward_merges_pnpm_created_flags_across_uuid_generations+ the pnpm_lock.rs e2e above).crates/socket-patch-core/src/vendor/lock_inventory.rs— FIFO wedge on all 15 raw lockfile reads (Cargo.lock, go.sum, npm shrinkwrap/package-lock, pnpm/rush/subspace locks, yarn ×2, bun, composer, Gemfile.lock ×2, uv/poetry/requirements,wired_vendor_integrity×2,gem_remotes): module-local guarded readers; inventories stay fail-soft (non-regular reads as absent) and symlinked lockfiles still read (handle-basedis_filefollows the link). Previously wedged scan's supplement, vendor's auto-fetch, and repair's reconstruction (tested byfifo_lockfiles_fail_fast_instead_of_wedging).crates/socket-patch-core/src/vendor/mod.rs— FIFO wedge inyarn_classic_berry_migration_risk: the probe runs unconditionally at envelope-finalize on everyvendor/scan --vendorrun, so a FIFO atyarn.lockorpackage.jsonwedged the whole run even in non-npm projects. New syncread_regular_file_to_string(O_NONBLOCK + handleis_fileon unix; plain open +is_fileelsewhere); semantics preserved — unreadable yarn.lock stays silent, unreadable package.json still fails toward the warning (tested byfifo_yarn_lock_never_wedges_probe/fifo_package_json_never_wedges_probe).crates/socket-patch-core/src/vendor/common.rs— two FIFO wedges:zip_matches_after_hashesreads the committed.jar/.nupkgviaopen_regular_file; a non-regular file reads as out-of-sync instead of wedging the maven/nuget in-sync probes — and every apply — forever (tested byfifo_archive_fails_fast_in_zip_matches_after_hashes).revert_lock_fragment_spliceguards the lock read and fails loudly with "cannot read {lock_file}"; a FIFO as poetry.lock/pdm.lock previously wedged remove/rollback (tested byfifo_lock_fails_fast_in_revert_lock_fragment_splice).crates/socket-patch-core/src/vendor/path.rs— SECURITY:sweep_vendor_dirslstat-guards the ecosystem root beforeread_dir. A committed.socket/vendor/npm -> <outside>symlink previously made the sweep enumerate uuid-shaped dirs at the target and let the CLI orphan sweep delete through the link (tested bysweep_never_follows_a_symlinked_ecosystem_dir). Known deliberate residual: only the final path component is lstat-checked per level — a symlinked.socketor.socket/vendorparent still resolves through, same as the pre-existing uuid-level guard.Vendor: PyPI family (pip/poetry/pdm/pipenv/uv/wheel) + toml_surgery — 12 fixes
crates/socket-patch-core/src/vendor/pypi.rs— FIFO planted aspyproject.tomlwedgeddetect_pypi_flavor(and every lockless-project vendor run) forever inopen(2); the read now goes throughopen_regular_file(O_NONBLOCK + regular-file check) and a FIFO reads as "no pyproject", falling through to requirements routing (tested byfifo_pyproject_does_not_wedge_flavor_detection).crates/socket-patch-core/src/vendor/pypi_pdm.rs— FIFO wedge on bothload_pdm_projectraw reads;pdm.lockFIFO now refuses fast (pypi_pdm_lock_parse_failed),pyproject.tomlFIFO degrades to no-pyproject/"transitive" diagnostics (tested byfifo_lock_or_pyproject_does_not_wedge_load).crates/socket-patch-core/src/vendor/pypi_pipenv.rs— FIFO wedge on bothPipfile.lockreads (load + revert); load refuses fast (pypi_pipenv_lock_parse_failed, NotFound still maps topypi_pipenv_no_lockfile), revert fails fast with "cannot read" (tested byfifo_lock_does_not_wedge_load_or_revert).crates/socket-patch-core/src/vendor/pypi_poetry.rs— FIFO wedge on bothload_poetry_projectraw reads; lock FIFO refuses fast (pypi_poetry_lock_parse_failed), pyproject FIFO degrades to no-pyproject (tested byfifo_lock_or_pyproject_does_not_wedge_load; also hosts the byte-exact end-to-end regressionwiring_leaves_files_keyed_subtable_lines_verbatimfor the sharedtoml_surgerysub-table fix below).crates/socket-patch-core/src/vendor/pypi_requirements.rs— three fixes:collect_requirements_files+revert_requirements): FIFO root refuses fast (pypi_no_requirements), FIFO include is skipped, revert fails fast (tested byfifo_requirements_does_not_wedge_wire_or_revert).\u{feff}six==1.16.0classified Absent → duplicate transitive line appended →pip install -rdouble-requirement error; the parser now strips one leading BOM from the first logical line's parse text only (records/revert stay byte-identical — tested bybom_first_line_pin_is_rewritten_not_duplicated).-rdev.txtinclude missed (pip's optparse accepts the no-space short form): the include was invisible, causing the same duplicate-append;include_targetnow follows-r<path>(tested byattached_short_form_include_is_followed).crates/socket-patch-core/src/vendor/pypi_uv.rs— FIFO wedge on all four raw reads (load_uv_projectandrevert_uv, pyproject + lock each); load refuses fast (pypi_uv_lock_parse_failed), revert fails fast — closes the last pypi FIFO twin (tested byfifo_lock_or_pyproject_does_not_wedge_load_or_revert, all four legs).crates/socket-patch-core/src/vendor/pypi_wheel.rs— two fixes:locate_installed_dist;entry_points.txt,direct_url.json, and every RECORD-member staging read inbuild_patched_wheel): FIFOs now surface as the existing refusals / fail the build closed / fall through as absent; the member read also reuses the open handle's metadata for the exec bit — one fewer stat (tested byfifo_record_or_wheel_does_not_wedge_locate,fifo_record_member_fails_closed_instead_of_wedging_build,fifo_direct_url_json_does_not_wedge_editable_probe,fifo_entry_points_does_not_wedge_build).+/!into_in the wheel filename (2.0.0+cu118→2.0.0_cu118), which pip/uv reject as an invalid wheel filename — the vendored artifact was uninstallable;escape_wheel_versionnow keeps+and!literally, matching bdist_wheel (tested bywheel_version_keeps_local_and_epoch_separators).crates/socket-patch-core/src/vendor/toml_surgery.rs— two fixes in the shared pypi text-surgery helpers:replace_files_arrayrewrote ANY unindentedfiles = [line — including[package.dependencies]/[package.extras]entries literally keyedfiles(poetry emits both; the PyPI packagefilesexists) — silently corrupting sub-tables into duplicate wheel arrays; it now passes everything from the first[package.header through verbatim and fails closed when only a sub-tablefilesexists (tested by the extendedreplace_files_array_handles_multi_line_inline_and_absent+ poetry'swiring_leaves_files_keyed_subtable_lines_verbatim; pdm units carry no sub-tables, so pdm output is byte-identical).remove_exact_line/remove_table_if_emptyrebuilt the file vialines()+join("\n"), LF-normalizing a CRLFpyproject.tomlwholesale on every uvvendor --revert(their only callers); both now splice by byte span with EOL-insensitive matching so untouched bytes survive verbatim — with two deliberate, test-pinned edge changes (a removed newline-less final line keeps the prior terminator; all-blank remainders are kept) (tested byremoval_helpers_preserve_foreign_line_endings).Vendor: Cargo + Gem + Composer — 8 fixes
crates/socket-patch-core/src/vendor/cargo.rs— two fixes:hosted_redirect_residueread the projectCargo.tomlwith a rawread_to_string→ now a guarded read (open_regular_file: O_NONBLOCK + fstatis_file), so a FIFO planted as the manifest fails fast and reads as "no residue" instead of wedging every wet cargo vendor run forever inopen(2)(tested bytest_fifo_cargo_toml_does_not_wedge_vendor).prune_empty_vendor_dirsstopped on ANYremove_dir(uuid_dir)error, but unwind pathsremove_treethe uuid dir first, soNotFoundaborted the prune →NotFoundnow continues to the parent levels while any other error (non-empty = live state) still stops; failed fresh vendors no longer leave empty.socket/vendor/cargo/+.socket/vendor/husks to commit (tested bytest_failed_fresh_vendor_leaves_no_vendor_husk).crates/socket-patch-core/src/vendor/cargo_config.rs— FIFO wedge: all three.cargo/config(.toml)read sites (edit_config,read_patch_entries,socket_registry_indexes) swapped to the guarded read; a FIFO squatting the config no longer wedges scan or wet vendor apply/remove, reads-only paths degrade to "no entries", andedit_configerrors loudly — never treats the squatted path as an empty config to rename a fresh file over (tested bytest_read_patch_entries_fifo_config_does_not_wedge,test_ensure_patch_entry_fifo_legacy_config_fails_fast,test_socket_registry_indexes_fifo_configs_do_not_wedge).crates/socket-patch-core/src/vendor/cargo_lock.rs— FIFO wedge:read_lock(single choke point for all five public fns) swapped to the guarded read; a FIFO planted asCargo.lockno longer wedges scan's probe, vendor mode detection, the version cross-check, or wet detach/restore — probes stay fail-safe (NoLockfile/None/0), edits refuse loudly withIo(tested byfifo_lock_fails_fast_instead_of_wedging, which exercises all five fns).crates/socket-patch-core/src/vendor/composer_lock.rs— FIFO wedge: all threecomposer.lockread sites (vendor's presence read,stranded_wired_packages,restore_lock_entry) swapped to the guarded read; vendor now refusesvendor_lockfile_missing/"unreadable" and revert fails loudly before deleting any artifacts instead of hanging forever (tested byfifo_lock_fails_fast_instead_of_wedging).crates/socket-patch-core/src/vendor/gem.rs— three fixes:reconstruct_gem_wiring×2, revert ×3) swapped to the guarded read, with a NotFound-vs-"unreadable" refusal-detail split mirroring composer (refusal codes unchanged); vendor, revert, and reconstruction no longer hang on a planted FIFO (tested byfifo_lock_fails_fast_instead_of_wedging).gem"rack"no separator,gem ("rack")space before paren) fell through to the transitive Append plan, writing a SECOND declaration that makes bundler hard-fail every install → a loosergem_call_mentions_nameprobe (the redirect rewriter'sdeclared_retwin) now gates the Append and refusesgemfile_declaration_not_editable(tested byunrecognized_gem_call_refuses_instead_of_duplicating).original: Nonethat carry-forward can never fill; revert reportedvendor_lock_entry_driftedon a byte-perfect restore →revert_lock_checksum_recordnow returns Ok(true) whenoriginalis None and the bare written line still stands (nothing to restore = pre-vendor state) (tested byrevendor_over_already_bare_checksum_reverts_without_drift).Vendor: Go + Maven + NuGet + registry_fetch — 13 fixes
crates/socket-patch-core/src/vendor/golang.rs— test-only: pins vendor-takes-over-hosted on the local-build leg (test_local_vendor_takes_over_hosted_replace: takeover, wiringoriginalrecords the hosted target verbatim, revert names the go.sum recovery). The production change — scoping the hosted-owned-replace refusal to apply'sGO_PATCHES_DIRcopy base — is inpatch/redirect/golang_local.rs(see that file's section), which also gainstest_apply_refuses_hosted_owned_replacesince the refusal is now conditional.crates/socket-patch-core/src/vendor/go_mod_edit.rs— two fixes:go.modwedged every caller (apply redirect + reconcile,--checkverify, vex scan) forever inopen(2)→ all three read paths now go throughopen_regular_file(O_NONBLOCK + is_file); reads stay fail-safe (empty vec /None), edits refuse loudly, NotFound legs unchanged (tested bytest_fifo_go_mod_fails_fast_instead_of_wedging).go.mod(git autocrlf on Windows), breaking the byte-identical ensure→drop round-trip → joins and the upsert append path now usedetect_eol(tested bytest_crlf_go_mod_preserves_line_endings).crates/socket-patch-core/src/vendor/go_sum_edit.rs— two fixes (pure&strtransform; depscan TS twins need matching ports):go.sumfiles wholesale on every hosted golang apply/revert →detect_eolat all three joins (tested bycrlf_go_sum_preserves_line_endings).SECURITY ERRORand the owner refused to repair) and a duplicated zip line masked a missing/go.modline → now requires exactly one of each wanted line and no other key-prefixed lines (tested byupsert_rewrites_when_stale_duplicate_key_line_coexists).crates/socket-patch-core/src/vendor/maven_repo.rs— FIFO-wedge guards on all 6 raw-read sites (project pom in vendor + revert,~/.m2pom + jar, committed leaf +.sha1insidecar_matches— the unguarded other half ofartifact_in_sync); a FIFO squatting the committed pom now reads as stale and the rebuild atomically renames a real pom over it; NotFound semantics preserved (fail fast instead of wedging vendor/hot-path/rebuild/revert forever; tested by 5 mkfifo+timeout tests:fifo_vendored_pom_fails_fast_and_rebuilds_on_hot_path,fifo_project_pom_fails_fast_in_vendor,fifo_cached_jar_fails_fast_in_local_rebuild,fifo_local_pom_fails_fast_in_acquire_upstream_pom,fifo_project_pom_fails_fast_in_revert).crates/socket-patch-core/src/vendor/nuget_feed.rs— two fixes:local_rebuild's cached nupkg, revert's config + lock), the maven twin; a FIFO squatting the committed nupkg reads as stale → atomic rebuild; a revert lock failure leaves the config wiring untouched for retry (tested by 6 mkfifo+timeout tests mirroring the maven set).<packageSources />madeparse_config_source_keysscan to EOF and harvest<add key>entries from unrelated sections as phantom catch-all sources — mapping*to a nonexistent source hard-fails every restore and suppressed the nuget.org seeding → no close tag now yields an empty key list, fail-closed (tested byself_closing_sources_do_not_harvest_foreign_add_keys).crates/socket-patch-core/src/vendor/registry_fetch.rs— five fixes:stage_local_artifactraw-read the committed vendored.tgz; the CLI caller only probesmetadata(), which a FIFO passes → fresh-clone re-vendor wedged forever; now guarded viaopen_regular_file(tested bystage_local_artifact_fifo_fails_fast_instead_of_wedging, which runs its own runtime on a detached thread so the unfixed code fails instead of hanging the harness).goproxy_basesplitGOPROXYonly on,; the Go spec also allows|— pipe-separated values built an unparseablehttps://a|b/...URL → splits on both (tested bygoproxy_base_splits_on_pipe_separator).10c0/) was refused only after downloading the whole tarball, breaking the "Unverifiable = decided before network I/O" contract, wasting a full download per package on yarn 2/3 locks, and flipping toFailedwhen the registry was unreachable → hoisted abovedownload, still gated onverify(tested byberry_foreign_cachekey_refuses_before_networkagainst an unroutable URL).file.size()-based caps were void against a crafted (poisoned-lockfile) artifact (~1000× deflate disk/CPU bomb). All 3 sites bounded:extract_zipcopies viatake(declared+1)+ refuses oncopied != declared;extract_zip_with_prefix(previously had no caps at all, reachable without the dirhash pre-pass via the golang service-download path when the service reports nodirhashH1) gains MAX_ENTRIES + declared per-entry/total caps + the same actual-bytes refusal;go_h1_of_zipcounts ACTUAL bytes per entry and in total while hashing (tested byzip_entry_lying_declared_size_fails_closed,module_zip_extraction_enforces_size_caps,go_h1_caps_actual_decompressed_bytes, via thepatch_declared_uncompressed_sizeheader-patching helper).\string continuation → single-spaced (tested bypypi_no_wheel_url_message_is_single_spaced).Follow-ups flagged by the review notes (not in this diff): berry_zip.rs gunzip actual-bytes cap twin (modified in another slice — confirm there); depscan TS ports of the go.mod/go.sum CRLF + already-check fixes; nuget
NuGet.configmiddle-casing probe gap (needs a Linux RED first).🤖 Generated with Claude Code