From c6df318fe6b2c5ca8e2a62c496b806bbbf047497 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 13 Oct 2026 23:52:34 +0000 Subject: [PATCH 1/3] Start fix for #823 slice 15 Assisted-by: Claude Code:claude-opus-5-5 From 1d0643560b2d2ed6d119d4ef95a7e1d7f5c48d6f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 00:02:25 +0000 Subject: [PATCH 2/3] Spawn npm/Bun test CLIs hermetically The npm and Bun suites started socket-patch with a bare Command::new and their own SOCKET_* scrubs, which had drifted from tests/common/hermetic.rs. e2e_npm and the Bun redirect suite never forced the config layer or update notifier off, so a developer's `socket login` or an ambient SOCKET_NO_UPDATE_CHECK=0 reached the child; the in-process Bun vendor FIFO helper skipped the stand-in HOME the rest of that file's spawns get through common::run_with_env. All three now start from hermetic::command. The Bun redirect suite keeps its shared bun/npm registry scrub through scrub_ambient_bun_tool_env, the variant that leaves the builder's SOCKET_* opt-outs alone, and the FIFO helper adds jvm_env::isolate_cli to match run_with_env. e2e_npm keeps its partial cache pins so the global auto-discovery test still sees real prefixes. The files leave PENDING_RAW_SPAWNS (15 -> 12), so the ratchet keeps them hermetic. Refs #823 Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/tests/e2e_npm.rs | 24 +++++-------------- .../tests/e2e_redirect_bun_build.rs | 10 ++++---- .../tests/spawn_env_hygiene.rs | 3 --- .../tests/vendor/in_process_vendor_bun.rs | 19 ++++----------- 4 files changed, 16 insertions(+), 40 deletions(-) diff --git a/crates/socket-patch-cli/tests/e2e_npm.rs b/crates/socket-patch-cli/tests/e2e_npm.rs index 74051c673..475921f0d 100644 --- a/crates/socket-patch-cli/tests/e2e_npm.rs +++ b/crates/socket-patch-cli/tests/e2e_npm.rs @@ -15,7 +15,7 @@ #[path = "common/mod.rs"] mod common; -use common::{binary, git_sha256}; +use common::{binary, git_sha256, hermetic}; use std::path::Path; use std::process::{Command, Output}; @@ -58,24 +58,12 @@ fn git_sha256_file(path: &Path) -> String { /// Run the CLI binary with the given args, setting `cwd` as the working dir. /// Returns `(exit_code, stdout, stderr)`. fn run(cwd: &Path, args: &[&str]) -> (i32, String, String) { - let mut cmd = Command::new(binary()); + // The hermetic `SOCKET_*` environment: no ambient value (SOCKET_DRY_RUN, + // SOCKET_GLOBAL_PREFIX, ...) changes what these tests exercise, no token + // (so the public proxy), and the config layer, update notifier and + // telemetry forced off. + let mut cmd = hermetic::command(&binary()); cmd.args(args).current_dir(cwd); - // The binary binds a wide `SOCKET_*` env surface (SOCKET_CWD, - // SOCKET_DRY_RUN, SOCKET_STRICT, SOCKET_ECOSYSTEMS, SOCKET_GLOBAL_PREFIX, - // ...). An ambient value silently changes what these tests exercise — - // SOCKET_DRY_RUN=true turns every real apply into a no-op, and - // SOCKET_GLOBAL_PREFIX flips commands into global mode, aiming mutations - // at the host's *real* global node_modules. Scrub the whole prefix so - // only the flags each test passes are in effect; removing - // SOCKET_API_TOKEN also forces the public proxy (free-tier). Telemetry - // opt-outs are deliberately kept so an opted-out dev stays opted out. - for (key, _) in std::env::vars_os() { - let name = key.to_string_lossy(); - if name.starts_with("SOCKET_") && !name.contains("TELEMETRY") && name != "SOCKET_NO_CONFIG" - { - cmd.env_remove(&key); - } - } // Partial cache isolation only — the global auto-discovery test below // needs the binary's `npm root -g` / `yarn global dir` / `pnpm root -g` // probes to resolve the REAL prefixes, which come out of $HOME, so a full diff --git a/crates/socket-patch-cli/tests/e2e_redirect_bun_build.rs b/crates/socket-patch-cli/tests/e2e_redirect_bun_build.rs index 6dc2cc1a3..cf2d70a3f 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_bun_build.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_bun_build.rs @@ -92,7 +92,7 @@ #[path = "common/mod.rs"] mod common; -use common::binary; +use common::{binary, hermetic}; use std::path::{Path, PathBuf}; use std::process::{Command, Output, Stdio}; @@ -306,11 +306,13 @@ fn run_socket(cwd: &Path, args: &[&str]) -> (i32, String, String) { run_socket_env(cwd, args, &[]) } -/// [`run_socket`] with extra env applied after the scrub. +/// [`run_socket`] with extra env applied after the scrub: the hermetic +/// `SOCKET_*` environment (`hermetic::command`) plus the bun suites' shared +/// scrub of every other ambient bun / npm registry setting. fn run_socket_env(cwd: &Path, args: &[&str], env: &[(String, String)]) -> (i32, String, String) { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic::command(&binary()); cmd.args(args).arg("--no-telemetry").current_dir(cwd); - cache_env::scrub_ambient_bun_env(&mut cmd); + cache_env::scrub_ambient_bun_tool_env(&mut cmd); for (k, v) in env { cmd.env(k, v); } diff --git a/crates/socket-patch-cli/tests/spawn_env_hygiene.rs b/crates/socket-patch-cli/tests/spawn_env_hygiene.rs index 3f77ada18..94502b139 100644 --- a/crates/socket-patch-cli/tests/spawn_env_hygiene.rs +++ b/crates/socket-patch-cli/tests/spawn_env_hygiene.rs @@ -33,15 +33,12 @@ const PENDING_RAW_SPAWNS: &[&str] = &[ "e2e_gem.rs", "e2e_hosted_production.rs", "e2e_maven.rs", - "e2e_npm.rs", - "e2e_redirect_bun_build.rs", "e2e_redirect_cargo_shapes.rs", "e2e_scan.rs", "e2e_socket_yml_policy.rs", "e2e_vendored_production.rs", "e2e_vex_vendor.rs", "rollback/rollback_multicopy_blob_gate.rs", - "vendor/in_process_vendor_bun.rs", "vendor/vendor_gem_lockfile_only_e2e.rs", "vendor/vendor_rerun_no_network_e2e.rs", ]; diff --git a/crates/socket-patch-cli/tests/vendor/in_process_vendor_bun.rs b/crates/socket-patch-cli/tests/vendor/in_process_vendor_bun.rs index 3e983f1a4..81f14e2d0 100644 --- a/crates/socket-patch-cli/tests/vendor/in_process_vendor_bun.rs +++ b/crates/socket-patch-cli/tests/vendor/in_process_vendor_bun.rs @@ -1619,21 +1619,10 @@ async fn scan_vendored_from_workspace_member_cwd_fetches_then_engine_refuses_loc fn run_with_deadline(root: &Path, argv: &[&str], deadline: Duration) -> (i32, String, String) { let out_path = root.join("child.stdout"); let err_path = root.join("child.stderr"); - let mut cmd = Command::new(common::binary()); - cmd.args(argv).current_dir(root); - for (key, _) in std::env::vars_os() { - let name = key.to_string_lossy(); - if name.starts_with("SOCKET_") - && !name.contains("TELEMETRY") - && name != "SOCKET_NO_CONFIG" - && name != "SOCKET_NO_UPDATE_CHECK" - { - cmd.env_remove(&key); - } - } - cmd.env("SOCKET_NO_CONFIG", "1") - .env("SOCKET_NO_UPDATE_CHECK", "1") - .env("SOCKET_TELEMETRY_DISABLED", "1") + let mut cmd = common::hermetic_command(&common::binary()); + common::jvm_env::isolate_cli(&mut cmd); + cmd.args(argv) + .current_dir(root) .stdin(Stdio::null()) .stdout(std::fs::File::create(&out_path).unwrap()) .stderr(std::fs::File::create(&err_path).unwrap()); From 2bf2c2e812e1679b6b0e71df5745939acbf9488c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 00:14:35 +0000 Subject: [PATCH 3/3] Keep real HOME and SOCKET_* off npm/Bun kids From /code-review high: the Bun redirect suite ran its bun fixture installs under the cache sandbox's home but the CLI under the real one, so a registry or scope in the developer's ~/.npmrc would make the two disagree. The CLI now gets the stand-in HOME (jvm_env::isolate_cli), as run_with_env gives every other suite. Its --no-telemetry flag goes: hermetic::command already forces telemetry off, and the flag would override a caller opting back in. e2e_npm's npm installs now drop ambient SOCKET_* vars too (hermetic::scrub_socket_vars), like the package-manager children of the other converted suites. Refs #823 Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/tests/e2e_npm.rs | 2 ++ .../tests/e2e_redirect_bun_build.rs | 14 +++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/crates/socket-patch-cli/tests/e2e_npm.rs b/crates/socket-patch-cli/tests/e2e_npm.rs index 475921f0d..3a7dce516 100644 --- a/crates/socket-patch-cli/tests/e2e_npm.rs +++ b/crates/socket-patch-cli/tests/e2e_npm.rs @@ -96,6 +96,7 @@ fn assert_run_ok(cwd: &Path, args: &[&str], context: &str) -> (String, String) { fn npm_run(cwd: &Path, args: &[&str]) { let mut cmd = Command::new("npm"); cmd.args(args).current_dir(cwd); + hermetic::scrub_socket_vars(&mut cmd); cache_env::isolate(&mut cmd); let out = cmd.output().expect("failed to run npm"); assert!( @@ -345,6 +346,7 @@ fn test_npm_global_lifecycle() { ]); // `--prefix` is a flag, so it still decides where the package lands; the // sandbox only moves the download cache off the caller's home. + hermetic::scrub_socket_vars(&mut cmd); cache_env::isolate(&mut cmd); let out = cmd.output().expect("failed to run npm install -g"); assert!( diff --git a/crates/socket-patch-cli/tests/e2e_redirect_bun_build.rs b/crates/socket-patch-cli/tests/e2e_redirect_bun_build.rs index cf2d70a3f..80e42bff0 100644 --- a/crates/socket-patch-cli/tests/e2e_redirect_bun_build.rs +++ b/crates/socket-patch-cli/tests/e2e_redirect_bun_build.rs @@ -300,19 +300,23 @@ fn bun_env(cwd: &Path, args: &[&str], cache_dir: &Path, env: &[(String, String)] cmd.output().expect("failed to run bun") } -/// The real binary with `--no-telemetry` appended: nothing in this suite -/// should ever post a telemetry event, mocked API or not. +/// The real binary. `hermetic::command` forces telemetry off: nothing in +/// this suite should ever post a telemetry event, mocked API or not. fn run_socket(cwd: &Path, args: &[&str]) -> (i32, String, String) { run_socket_env(cwd, args, &[]) } /// [`run_socket`] with extra env applied after the scrub: the hermetic -/// `SOCKET_*` environment (`hermetic::command`) plus the bun suites' shared -/// scrub of every other ambient bun / npm registry setting. +/// `SOCKET_*` environment (`hermetic::command`), the bun suites' shared +/// scrub of every other ambient bun / npm registry setting, and a stand-in +/// `HOME` (`jvm_env::isolate_cli`). The `bun` fixture children run under the +/// cache sandbox's home, so the CLI must not read the developer's real +/// `~/.npmrc` either: a registry or scope there would make the two disagree. fn run_socket_env(cwd: &Path, args: &[&str], env: &[(String, String)]) -> (i32, String, String) { let mut cmd = hermetic::command(&binary()); - cmd.args(args).arg("--no-telemetry").current_dir(cwd); + cmd.args(args).current_dir(cwd); cache_env::scrub_ambient_bun_tool_env(&mut cmd); + common::jvm_env::isolate_cli(&mut cmd); for (k, v) in env { cmd.env(k, v); }