From 20bb513a9088620060ed28b718e25b38d0ef9554 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 03:59:14 +0000 Subject: [PATCH 1/3] Start fix for #823 (e2e_maven slice) Assisted-by: Claude Code:claude-opus-5-5 From 34d64f0eedf1569261e719d03d48238a0599340b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 03:59:14 +0000 Subject: [PATCH 2/3] Spawn e2e_maven children hermetically e2e_maven started the CLI with a bare Command::new and a private sweep of a handful of SOCKET_* names. Any other ambient value reached the child: on main, a shell exporting SOCKET_ECOSYSTEMS=cargo fails two of its scans, and SOCKET_NO_CONFIG=0 lets a developer's real socket login authenticate the test child. Its run() helper now builds the child with hermetic::command, scrubs an activated venv and isolates the JVM env the way e2e_cargo does, and keeps its own fixture HOME and cache pins. The file leaves PENDING_RAW_SPAWNS so the spawn_env_hygiene ratchet keeps it there. Refs #823 Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/tests/e2e_maven.rs | 31 +++++++------------ .../tests/spawn_env_hygiene.rs | 1 - 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/crates/socket-patch-cli/tests/e2e_maven.rs b/crates/socket-patch-cli/tests/e2e_maven.rs index 5878e539b..63ea2442b 100644 --- a/crates/socket-patch-cli/tests/e2e_maven.rs +++ b/crates/socket-patch-cli/tests/e2e_maven.rs @@ -15,10 +15,10 @@ #[path = "common/mod.rs"] mod common; -use common::binary; +use common::{binary, hermetic}; use std::path::Path; -use std::process::{Command, Output}; +use std::process::Output; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -46,11 +46,12 @@ async fn start_proxy() -> MockServer { /// Run the binary as a blocking subprocess (off the async runtime so the /// in-test proxy can service its requests concurrently), pinned to `proxy_url`. /// -/// `SOCKET_API_TOKEN` is stripped so the binary deterministically takes the -/// public-proxy path (an ambient token would flip it onto the authenticated -/// API, bypassing `--proxy-url`), and every other variable that could -/// redirect the API elsewhere or disable it is scrubbed so an ambient value -/// can't quietly change what the scan reports. +/// The child gets the hermetic `SOCKET_*` environment (no token, so the +/// binary deterministically takes the public-proxy path `--proxy-url` +/// pins, and no ambient variable can redirect or disable the API). The +/// scans are unscoped, so an activated venv is scrubbed and the JVM env is +/// isolated (`jvm_env::isolate_cli`); `HOME` then moves to a sibling of the +/// fixture repo so no Coursier / Ivy cache exists below it. async fn run(args: &[&str], cwd: &Path, m2_repo: &Path, proxy_url: &str) -> Output { let mut args: Vec = args.iter().map(|s| s.to_string()).collect(); args.extend(["--proxy-url".to_string(), proxy_url.to_string()]); @@ -58,8 +59,10 @@ async fn run(args: &[&str], cwd: &Path, m2_repo: &Path, proxy_url: &str) -> Outp let m2_repo = m2_repo.to_path_buf(); tokio::task::spawn_blocking(move || { let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect(); - Command::new(binary()) - .args(&arg_refs) + let mut cmd = hermetic::command(&binary()); + hermetic::scrub_extra(&mut cmd, &[hermetic::Extra::Venv]); + common::jvm_env::isolate_cli(&mut cmd); + cmd.args(&arg_refs) .current_dir(&cwd) // Point the crawler at the fake local repo. .env("MAVEN_REPO_LOCAL", &m2_repo) @@ -70,17 +73,7 @@ async fn run(args: &[&str], cwd: &Path, m2_repo: &Path, proxy_url: &str) -> Outp .env("USERPROFILE", m2_repo.with_file_name("no-home")) .env("XDG_CACHE_HOME", m2_repo.with_file_name("no-xdg-cache")) .env("LOCALAPPDATA", m2_repo.with_file_name("no-localappdata")) - .env_remove("COURSIER_CACHE") - .env_remove("SBT_OPTS") - .env_remove("JAVA_OPTS") - // Keep the run hermetic: no ambient token, no inherited repo path. - .env_remove("SOCKET_API_TOKEN") - .env_remove("SOCKET_CLI_API_TOKEN") .env_remove("M2_HOME") - .env_remove("SOCKET_API_URL") - .env_remove("SOCKET_OFFLINE") - .env_remove("SOCKET_PROXY_URL") - .env_remove("SOCKET_BATCH_SIZE") .output() .expect("Failed to run socket-patch binary") }) diff --git a/crates/socket-patch-cli/tests/spawn_env_hygiene.rs b/crates/socket-patch-cli/tests/spawn_env_hygiene.rs index 23ac28316..ed37e4281 100644 --- a/crates/socket-patch-cli/tests/spawn_env_hygiene.rs +++ b/crates/socket-patch-cli/tests/spawn_env_hygiene.rs @@ -31,7 +31,6 @@ use serial_test::serial; const PENDING_RAW_SPAWNS: &[&str] = &[ "apply/apply_network.rs", "e2e_hosted_production.rs", - "e2e_maven.rs", "e2e_redirect_cargo_shapes.rs", "e2e_scan.rs", "e2e_socket_yml_policy.rs", From be57c3dbd1fba54c699de1ad746e97f4a6dbd293 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 04:04:15 +0000 Subject: [PATCH 3/3] Pin e2e_maven's Gradle home per fixture The shared stand-in GRADLE_USER_HOME outlives test runs, so a cache left there would change the Gradle scan's package count on that machine only. Pin it next to the fixture's own HOME, note why the home pins must follow isolate_cli, and use hermetic::binary_command. Assisted-by: Claude Code:claude-opus-5-5 --- crates/socket-patch-cli/tests/e2e_maven.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/socket-patch-cli/tests/e2e_maven.rs b/crates/socket-patch-cli/tests/e2e_maven.rs index 63ea2442b..a455fcc85 100644 --- a/crates/socket-patch-cli/tests/e2e_maven.rs +++ b/crates/socket-patch-cli/tests/e2e_maven.rs @@ -15,7 +15,7 @@ #[path = "common/mod.rs"] mod common; -use common::{binary, hermetic}; +use common::hermetic; use std::path::Path; use std::process::Output; @@ -50,8 +50,10 @@ async fn start_proxy() -> MockServer { /// binary deterministically takes the public-proxy path `--proxy-url` /// pins, and no ambient variable can redirect or disable the API). The /// scans are unscoped, so an activated venv is scrubbed and the JVM env is -/// isolated (`jvm_env::isolate_cli`); `HOME` then moves to a sibling of the -/// fixture repo so no Coursier / Ivy cache exists below it. +/// isolated (`jvm_env::isolate_cli`). The home pins below must stay after +/// that call (the last write for a name wins): they replace its shared +/// stand-in home with siblings of this fixture's repo, so no Coursier, Ivy +/// or Gradle cache exists below them. async fn run(args: &[&str], cwd: &Path, m2_repo: &Path, proxy_url: &str) -> Output { let mut args: Vec = args.iter().map(|s| s.to_string()).collect(); args.extend(["--proxy-url".to_string(), proxy_url.to_string()]); @@ -59,7 +61,7 @@ async fn run(args: &[&str], cwd: &Path, m2_repo: &Path, proxy_url: &str) -> Outp let m2_repo = m2_repo.to_path_buf(); tokio::task::spawn_blocking(move || { let arg_refs: Vec<&str> = args.iter().map(String::as_str).collect(); - let mut cmd = hermetic::command(&binary()); + let mut cmd = hermetic::binary_command(); hermetic::scrub_extra(&mut cmd, &[hermetic::Extra::Venv]); common::jvm_env::isolate_cli(&mut cmd); cmd.args(&arg_refs) @@ -73,6 +75,7 @@ async fn run(args: &[&str], cwd: &Path, m2_repo: &Path, proxy_url: &str) -> Outp .env("USERPROFILE", m2_repo.with_file_name("no-home")) .env("XDG_CACHE_HOME", m2_repo.with_file_name("no-xdg-cache")) .env("LOCALAPPDATA", m2_repo.with_file_name("no-localappdata")) + .env("GRADLE_USER_HOME", m2_repo.with_file_name("no-gradle")) .env_remove("M2_HOME") .output() .expect("Failed to run socket-patch binary")