diff --git a/crates/socket-patch-cli/tests/common/hermetic.rs b/crates/socket-patch-cli/tests/common/hermetic.rs index e434d269f..8c7859040 100644 --- a/crates/socket-patch-cli/tests/common/hermetic.rs +++ b/crates/socket-patch-cli/tests/common/hermetic.rs @@ -38,6 +38,7 @@ const HOSTILE_SEEDS: &[(&str, &str)] = &[ ("SOCKET_GLOBAL", "true"), ("SOCKET_GLOBAL_PREFIX", "/nonexistent"), ("SOCKET_DRY_RUN", "true"), + ("SOCKET_ECOSYSTEMS", "cargo"), ("SOCKET_MANIFEST_PATH", "/nonexistent/manifest.json"), ("SOCKET_JSON", "true"), ("SOCKET_SILENT", "true"), diff --git a/crates/socket-patch-cli/tests/repair/coverage_fix_repair_vendor_predelete.rs b/crates/socket-patch-cli/tests/repair/coverage_fix_repair_vendor_predelete.rs index 148054e85..58bf83cff 100644 --- a/crates/socket-patch-cli/tests/repair/coverage_fix_repair_vendor_predelete.rs +++ b/crates/socket-patch-cli/tests/repair/coverage_fix_repair_vendor_predelete.rs @@ -6,10 +6,9 @@ //! forensic bytes must come back. Gem fixtures modeled on //! repair_vendor_e2e's. -use crate::common::{binary, git_sha256}; +use crate::common::{binary, git_sha256, hermetic_command}; use std::path::{Path, PathBuf}; -use std::process::Command; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -144,23 +143,12 @@ fn run_cli(root: &Path, mock_uri: &str, argv: &[&str]) -> (i32, String, String) "--org", ORG_SLUG, ]); - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.args(&full).current_dir(root); - // Scrub every ambient `SOCKET_*` var (same rationale as - // `covgap_commands_repair.rs::socket_cmd`: an ambient + // `hermetic_command` scrubs every ambient `SOCKET_*` var: an ambient // `SOCKET_DRY_RUN`/`SOCKET_OFFLINE`/`SOCKET_ECOSYSTEMS` would flip the - // fixture setup and repair legs before the behavior under test runs). - for (name, _) in std::env::vars_os() { - if name.to_string_lossy().starts_with("SOCKET_") - && name.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(name); - } - } - let out = cmd - .env("SOCKET_TELEMETRY_DISABLED", "1") - .output() - .expect("run"); + // fixture setup and repair legs before the behavior under test runs. + let out = cmd.output().expect("run"); ( out.status.code().unwrap_or(-1), String::from_utf8_lossy(&out.stdout).into_owned(), diff --git a/crates/socket-patch-cli/tests/repair/covgap_commands_repair.rs b/crates/socket-patch-cli/tests/repair/covgap_commands_repair.rs index 4231a736f..8030a5f17 100644 --- a/crates/socket-patch-cli/tests/repair/covgap_commands_repair.rs +++ b/crates/socket-patch-cli/tests/repair/covgap_commands_repair.rs @@ -23,6 +23,8 @@ use std::path::{Path, PathBuf}; use std::process::Command; +use crate::common::hermetic_command; + use sha2::{Digest, Sha256}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -34,20 +36,13 @@ fn binary() -> PathBuf { } /// A `socket-patch` command rooted at `cwd` with every ambient `SOCKET_*` -/// env var scrubbed (same rationale as `repair_invariants.rs`: an ambient +/// env var scrubbed by `hermetic_command` (same rationale as `repair_invariants.rs`: an ambient /// `SOCKET_OFFLINE`/`SOCKET_JSON`/`SOCKET_SILENT` would flip the very /// loud-vs-quiet branches this suite exists to pin). Tests re-seed only /// what they deliberately control via `.env()` after this scrub. fn socket_cmd(cwd: &Path) -> Command { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.current_dir(cwd); - for (name, _) in std::env::vars_os() { - if name.to_string_lossy().starts_with("SOCKET_") - && name.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(name); - } - } cmd } @@ -305,7 +300,7 @@ fn repair_offline_warning_truncates_missing_list_after_five() { /// The dry-run preview lists at most 10 missing ids and folds the rest into /// " ... and N more". With 12 missing that's 10 items + 2. (No `--offline`: /// the dry-run branch returns before any download I/O, so the run stays -/// hermetic; telemetry is pinned off explicitly since the run is not +/// hermetic; `socket_cmd` keeps telemetry off since the run is not /// airgapped by flag.) #[test] fn repair_dry_run_preview_truncates_missing_list_after_ten() { @@ -314,7 +309,6 @@ fn repair_dry_run_preview_truncates_missing_list_after_ten() { let out = socket_cmd(tmp.path()) .args(["repair", "--dry-run"]) - .env("SOCKET_TELEMETRY_DISABLED", "1") .output() .expect("run socket-patch"); let stdout = String::from_utf8_lossy(&out.stdout); @@ -717,7 +711,6 @@ fn run_cli(root: &Path, mock_uri: &str, argv: &[&str], json: bool) -> (i32, Stri "--org", ORG_SLUG, ]); - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); let out = cmd.output().expect("run socket-patch"); ( out.status.code().unwrap_or(-1), diff --git a/crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs b/crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs index f27016f5b..dbc946b8e 100644 --- a/crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs +++ b/crates/socket-patch-cli/tests/repair/covgap_commands_repair_vendor.rs @@ -14,7 +14,8 @@ //! build by hand-migrating the fixture with [`to_legacy_manifest_mode`]. use std::path::{Path, PathBuf}; -use std::process::Command; + +use crate::common::hermetic_command; use sha2::{Digest, Sha256}; use wiremock::matchers::{method, path}; @@ -368,22 +369,13 @@ fn run_cli_with( "--org", ORG_SLUG, ]); - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.args(&full).current_dir(root); - // Scrub every ambient `SOCKET_*` var (same rationale as - // `covgap_commands_repair.rs::socket_cmd`: an ambient + // `hermetic_command` scrubs every ambient `SOCKET_*` var: an ambient // `SOCKET_JSON`/`SOCKET_SILENT`/`SOCKET_OFFLINE` would flip the very - // loud-vs-quiet and online-rebuild branches this suite pins). Tests + // loud-vs-quiet and online-rebuild branches this suite pins. Tests // re-seed only what they deliberately control via `envs`. - for (name, _) in std::env::vars_os() { - if name.to_string_lossy().starts_with("SOCKET_") - && name.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(name); - } - } - cmd.env("SOCKET_TELEMETRY_DISABLED", "1") - .env("SOCKET_API_CONCURRENCY", "1"); + cmd.env("SOCKET_API_CONCURRENCY", "1"); for (k, v) in envs { cmd.env(k, v); } diff --git a/crates/socket-patch-cli/tests/repair/repair_invariants.rs b/crates/socket-patch-cli/tests/repair/repair_invariants.rs index eb1679690..26c808615 100644 --- a/crates/socket-patch-cli/tests/repair/repair_invariants.rs +++ b/crates/socket-patch-cli/tests/repair/repair_invariants.rs @@ -7,6 +7,8 @@ use std::path::{Path, PathBuf}; use std::process::Command; +use crate::common::hermetic_command; + use sha2::{Digest, Sha256}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -18,7 +20,7 @@ fn binary() -> PathBuf { } /// A `socket-patch` command rooted at `cwd` with every ambient `SOCKET_*` -/// env var scrubbed, so every assertion exercises the flag/argv path and +/// env var scrubbed (`hermetic_command`), so every assertion exercises the flag/argv path and /// nothing the ambient environment happened to leak in: /// * an ambient `SOCKET_OFFLINE` would make every `--offline` test pass even /// if the `--offline` *flag* path regressed (the binary would be offline @@ -37,15 +39,8 @@ fn binary() -> PathBuf { /// covers. Tests re-seed (via `.env()`, after this scrub) only the handful /// they deliberately control. fn socket_cmd(cwd: &Path) -> Command { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.current_dir(cwd); - for (name, _) in std::env::vars_os() { - if name.to_string_lossy().starts_with("SOCKET_") - && name.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(name); - } - } cmd } @@ -794,11 +789,6 @@ async fn repair_json_reports_unresolved_org_fallback_in_warnings() { .expect(1) .mount(&mock) .await; - Mock::given(method("POST")) - .and(path("/patch/telemetry")) - .respond_with(ResponseTemplate::new(201)) - .mount(&mock) - .await; let tmp = tempfile::tempdir().expect("tempdir"); let socket = tmp.path().join(".socket"); @@ -819,7 +809,6 @@ async fn repair_json_reports_unresolved_org_fallback_in_warnings() { "--api-token", &token, ]) - .env("SOCKET_NO_CONFIG", "1") .output() .expect("run socket-patch"); let stdout = String::from_utf8_lossy(&out.stdout).to_string(); @@ -995,7 +984,7 @@ fn repair_honors_manifest_path_override() { async fn repair_silent_still_reports_download_failures_on_stderr() { // Mock API with NO blob route mounted: every fetch 404s → counted as // failed ("Blob not found on server"), with no retries or live egress - // (telemetry also resolves to this mock via SOCKET_API_URL). + // (`hermetic_command` keeps telemetry off). let mock = MockServer::start().await; let tmp = tempfile::tempdir().expect("tempdir"); diff --git a/crates/socket-patch-cli/tests/scan/coverage_fix_scan_discovery_corrupt_ledger.rs b/crates/socket-patch-cli/tests/scan/coverage_fix_scan_discovery_corrupt_ledger.rs index 054e7cde6..38fe5475f 100644 --- a/crates/socket-patch-cli/tests/scan/coverage_fix_scan_discovery_corrupt_ledger.rs +++ b/crates/socket-patch-cli/tests/scan/coverage_fix_scan_discovery_corrupt_ledger.rs @@ -8,10 +8,9 @@ //! Modeled on `scan_vendor_e2e.rs` (mock API + real fixture through the //! built binary). -use crate::common::{binary, git_sha256}; +use crate::common::{binary, git_sha256, hermetic_command}; use std::path::Path; -use std::process::Command; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -81,19 +80,11 @@ fn seed_vendored_cargo_with_corrupt_ledger(root: &Path) { std::fs::write(socket.join("vendor/state.json"), b"{\"entries\": {").unwrap(); } -/// Spawn the built binary in `root` with the ambient `SOCKET_*` surface -/// scrubbed and telemetry killed (same posture as `scan_vendor_e2e.rs`). +/// Spawn the built binary in `root` with the hermetic `SOCKET_*` +/// environment (ambient vars scrubbed, telemetry killed). fn run_cli(root: &Path, argv: &[&str]) -> (i32, String, String) { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.args(argv).current_dir(root); - for (key, _) in std::env::vars_os() { - if key.to_string_lossy().starts_with("SOCKET_") - && key.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(&key); - } - } - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); let out = cmd.output().expect("run"); ( out.status.code().unwrap_or(-1), diff --git a/crates/socket-patch-cli/tests/scan/covgap_commands_fetch_stage.rs b/crates/socket-patch-cli/tests/scan/covgap_commands_fetch_stage.rs index 99d030c12..5737eacf5 100644 --- a/crates/socket-patch-cli/tests/scan/covgap_commands_fetch_stage.rs +++ b/crates/socket-patch-cli/tests/scan/covgap_commands_fetch_stage.rs @@ -17,7 +17,8 @@ //! human-mode (non-quiet) output shape and the mem-stager arms. use std::path::{Path, PathBuf}; -use std::process::Command; + +use crate::common::hermetic_command; use sha2::{Digest, Sha256}; use wiremock::matchers::{method, path}; @@ -43,21 +44,12 @@ fn git_sha256(content: &[u8]) -> String { hex::encode(hasher.finalize()) } -/// Spawn the built binary in `root` with a scrubbed `SOCKET_*` surface -/// (prefix scrub — fixed lists rot), the telemetry kill-switch forced, -/// and `extra_env` injected last so deliberate seeds survive the scrub. -/// Mirrors `scan_vendor_e2e.rs::run_cli_env`. +/// Spawn the built binary in `root` with the hermetic `SOCKET_*` +/// environment (ambient vars scrubbed, telemetry kill-switch forced) and +/// `extra_env` injected last so deliberate seeds survive the scrub. fn run_cli_env(root: &Path, argv: &[&str], extra_env: &[(&str, &str)]) -> (i32, String, String) { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.args(argv).current_dir(root); - for (key, _) in std::env::vars_os() { - if key.to_string_lossy().starts_with("SOCKET_") - && key.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(&key); - } - } - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); for (k, v) in extra_env { cmd.env(k, v); } diff --git a/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs b/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs index b57471277..236a9bda1 100644 --- a/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs +++ b/crates/socket-patch-cli/tests/scan/covgap_commands_scan_vendor_flow.rs @@ -1,13 +1,12 @@ //! Scan vendoring previews, lock failures and corrupt legacy manifests. -use crate::common::{binary, git_sha256}; +use crate::common::{binary, git_sha256, hermetic_command}; #[path = "../prebuilt_common/mod.rs"] mod prebuilt_common; use std::fs::OpenOptions; use std::path::Path; -use std::process::Command; use fs2::FileExt; use wiremock::matchers::{method, path}; @@ -157,21 +156,12 @@ async fn mount_empty_discovery(mock: &MockServer) { .await; } -/// Spawn the built binary in `root`, hermetically (the -/// `scan_vendor_e2e.rs` pattern): scrub the ambient `SOCKET_*` surface so -/// a developer's shell can't steer the child, then force the telemetry -/// kill-switch so no run ever phones the live API. +/// Spawn the built binary in `root` with the hermetic `SOCKET_*` +/// environment: a developer's shell can't steer the child, and the forced +/// telemetry kill-switch keeps any run from phoning the live API. fn run_cli(root: &Path, argv: &[&str]) -> (i32, String, String) { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.args(argv).current_dir(root); - for (key, _) in std::env::vars_os() { - if key.to_string_lossy().starts_with("SOCKET_") - && key.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(&key); - } - } - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); let out = cmd.output().expect("run"); ( out.status.code().unwrap_or(-1), diff --git a/crates/socket-patch-cli/tests/scan/hosted_management_refusals.rs b/crates/socket-patch-cli/tests/scan/hosted_management_refusals.rs index e6d756302..3df5ac315 100644 --- a/crates/socket-patch-cli/tests/scan/hosted_management_refusals.rs +++ b/crates/socket-patch-cli/tests/scan/hosted_management_refusals.rs @@ -10,6 +10,8 @@ use std::path::Path; use std::process::Command; +use crate::common::hermetic; + use serde_json::Value; use wiremock::MockServer; @@ -17,14 +19,7 @@ const PATCH: &str = "11111111-1111-4111-8111-111111111111"; const GRANT: &str = "22222222-2222-4222-8222-222222222222"; fn cli() -> Command { - let mut cmd = Command::new(env!("CARGO_BIN_EXE_socket-patch")); - for (key, _) in std::env::vars() { - if key.starts_with("SOCKET_") && key != "SOCKET_NO_CONFIG" { - cmd.env_remove(key); - } - } - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); - cmd + hermetic::binary_command() } fn lock(resolved: &str, integrity: &str) -> String { diff --git a/crates/socket-patch-cli/tests/scan/scan_batch_sizing_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_batch_sizing_e2e.rs index 55feb02ff..2ebd1c701 100644 --- a/crates/socket-patch-cli/tests/scan/scan_batch_sizing_e2e.rs +++ b/crates/socket-patch-cli/tests/scan/scan_batch_sizing_e2e.rs @@ -6,13 +6,15 @@ //! mid-run downgrade to the proxy keeps the chunk boundaries the run started //! with. //! -//! Subprocess runs scrub the `SOCKET_*` environment (the -//! `scan_ordered_concurrency_e2e.rs::scrubbed_cli` pattern) so ambient -//! configuration cannot reroute the branch under test. +//! Subprocess runs use the hermetic `SOCKET_*` environment +//! (`common/hermetic.rs`) so ambient configuration cannot reroute the +//! branch under test. use std::path::Path; use std::process::Command; +use crate::common::hermetic::{self, Extra}; + use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -23,15 +25,8 @@ const PROXY_BATCH_ROUTE: &str = "/patch/batch"; const BODY_CAP: usize = 256 * 1024; fn scrubbed_cli() -> Command { - let mut cmd = Command::new(env!("CARGO_BIN_EXE_socket-patch")); - for (key, _) in std::env::vars_os() { - let name = key.to_string_lossy(); - if name.starts_with("SOCKET_") && name != "SOCKET_NO_CONFIG" { - cmd.env_remove(&key); - } - } - cmd.env_remove("VIRTUAL_ENV"); - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); + let mut cmd = hermetic::binary_command(); + hermetic::scrub_extra(&mut cmd, &[Extra::Venv]); cmd } @@ -48,8 +43,7 @@ fn run_scan( let mut cmd = scrubbed_cli(); cmd.arg("scan") .args(["--json", "--cwd", cwd.to_str().unwrap()]) - .args(args) - .env("SOCKET_NO_CONFIG", "1"); + .args(args); if token { cmd.args([ "--api-url", @@ -243,8 +237,7 @@ async fn a_mid_run_downgrade_keeps_the_500_purl_chunks() { cmd.arg("scan") .args(["--json", "--cwd", tmp.path().to_str().unwrap()]) .args(["--api-url", &server.uri(), "--proxy-url", &server.uri()]) - .args(["--api-token", "fake-token-for-test", "--org", ORG]) - .env("SOCKET_NO_CONFIG", "1"); + .args(["--api-token", "fake-token-for-test", "--org", ORG]); let out = cmd.output().expect("run socket-patch scan"); let stdout = String::from_utf8_lossy(&out.stdout); let stderr = String::from_utf8_lossy(&out.stderr); diff --git a/crates/socket-patch-cli/tests/scan/scan_ecosystems_scope_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_ecosystems_scope_e2e.rs index 853dd2833..eae02684c 100644 --- a/crates/socket-patch-cli/tests/scan/scan_ecosystems_scope_e2e.rs +++ b/crates/socket-patch-cli/tests/scan/scan_ecosystems_scope_e2e.rs @@ -19,10 +19,10 @@ //! installed (`CARGO_HOME` points at an empty dir, so the cargo crawl finds //! nothing — hermetic, and a lockfile-only cargo entry either way). -use crate::common::binary; +use crate::common::hermetic::{self, Extra}; +use crate::common::{binary, hermetic_command}; use std::path::Path; -use std::process::Command; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -33,23 +33,15 @@ const NPM_LOCK_ONLY: &str = "lock-only-dep"; const CARGO_LOCK_ONLY: &str = "lockonly-crate"; const INSTALLED_CRATE: &str = "installed-crate"; -/// `scan --json` against `api_url` with the `SOCKET_*` environment -/// scrubbed (except the workspace-pinned `SOCKET_NO_CONFIG`), `VIRTUAL_ENV` +/// `scan --json` against `api_url` with the hermetic `SOCKET_*` +/// environment, `VIRTUAL_ENV` /// removed (the python crawler honors it first) and `CARGO_HOME` pinned to /// an empty dir under `cwd`. fn run_scan(cwd: &Path, api_url: &str, extra: &[&str]) -> (i32, String, String) { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.arg("scan").current_dir(cwd); - for (key, _) in std::env::vars_os() { - if key.to_string_lossy().starts_with("SOCKET_") - && key.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(&key); - } - } - cmd.env_remove("VIRTUAL_ENV"); + hermetic::scrub_extra(&mut cmd, &[Extra::Venv]); cmd.env("CARGO_HOME", cwd.join(".cargo-home")); - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); let out = cmd .args([ "--json", diff --git a/crates/socket-patch-cli/tests/scan/scan_invariants.rs b/crates/socket-patch-cli/tests/scan/scan_invariants.rs index fba8330c7..2a9509064 100644 --- a/crates/socket-patch-cli/tests/scan/scan_invariants.rs +++ b/crates/socket-patch-cli/tests/scan/scan_invariants.rs @@ -7,10 +7,9 @@ //! depending on the live Socket API. The real-API end-to-end suite //! lives in `e2e_scan.rs` (gated behind `#[ignore]`). -use crate::common::binary; +use crate::common::{binary, hermetic_command}; use std::path::Path; -use std::process::Command; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -48,7 +47,7 @@ fn run_scan(cwd: &Path, api_url: &str, extra: &[&str]) -> (i32, String, String) ORG_SLUG, ]; args.extend_from_slice(extra); - let out = Command::new(binary()) + let out = hermetic_command(&binary()) .args(&args) .current_dir(cwd) .output() diff --git a/crates/socket-patch-cli/tests/scan/scan_ordered_concurrency_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_ordered_concurrency_e2e.rs index a09c80c17..605a98613 100644 --- a/crates/socket-patch-cli/tests/scan/scan_ordered_concurrency_e2e.rs +++ b/crates/socket-patch-cli/tests/scan/scan_ordered_concurrency_e2e.rs @@ -10,14 +10,16 @@ //! output. Where a pure oracle exists, the output is also compared with a //! zero-latency run of the same fixture. //! -//! Subprocess runs scrub the `SOCKET_*` environment (the -//! `in_process_redirect.rs::scrubbed_cli` pattern) so ambient -//! configuration cannot reroute the branch under test. +//! Subprocess runs use the hermetic `SOCKET_*` environment +//! (`common/hermetic.rs`) so ambient configuration cannot reroute the +//! branch under test. use std::path::Path; use std::process::Command; use std::time::Duration; +use crate::common::hermetic::{self, Extra}; + use wiremock::matchers::{body_string_contains, method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -55,21 +57,8 @@ fn encode_purl(purl: &str) -> String { } fn scrubbed_cli() -> Command { - let mut cmd = Command::new(env!("CARGO_BIN_EXE_socket-patch")); - cmd.env("SOCKET_DRY_RUN", "true") - .env("SOCKET_ECOSYSTEMS", "cargo") - .env("SOCKET_MANIFEST_PATH", "/nonexistent/manifest.json") - .env_remove("SOCKET_DRY_RUN") - .env_remove("SOCKET_ECOSYSTEMS") - .env_remove("SOCKET_MANIFEST_PATH"); - for (key, _) in std::env::vars_os() { - let name = key.to_string_lossy(); - if name.starts_with("SOCKET_") && name != "SOCKET_NO_CONFIG" { - cmd.env_remove(&key); - } - } - cmd.env_remove("VIRTUAL_ENV"); - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); + let mut cmd = hermetic::binary_command(); + hermetic::scrub_extra(&mut cmd, &[Extra::Venv]); cmd } @@ -516,8 +505,7 @@ fn run_scan_anonymous( proxy, ]) .args(args) - .env("SOCKET_NO_API_TOKEN", "1") - .env("SOCKET_NO_CONFIG", "1"); + .env("SOCKET_NO_API_TOKEN", "1"); for (key, value) in extra { cmd.env(key, value); } diff --git a/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs index 5e2a5db18..a0f7d7adc 100644 --- a/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs +++ b/crates/socket-patch-cli/tests/scan/scan_paths_e2e.rs @@ -2,9 +2,9 @@ //! a local `wiremock` server. //! //! Spawns the real `socket-patch` binary (same recipe as -//! `scan_invariants.rs`, with `rollback_invariants.rs`'s SOCKET_* env -//! scrub) and pins the CONTRACT of path-scoped scans (the scoping bullets -//! describe agent mode, where PATHS are installed-path globs): +//! `scan_invariants.rs`, with the hermetic `SOCKET_*` environment) and +//! pins the CONTRACT of path-scoped scans (the scoping bullets describe +//! agent mode, where PATHS are installed-path globs): //! //! * scoping narrows the API QUERY (batch-POST body oracle) and the //! envelope counters, echoing the patterns in an always-present `paths` @@ -20,7 +20,8 @@ //! under `--json` or `--vex`, or an unparseable glob is a usage error //! (exit 2). -use crate::common::binary; +use crate::common::hermetic::{self, Extra}; +use crate::common::{binary, hermetic_command}; use std::path::{Path, PathBuf}; use std::process::Command; @@ -32,26 +33,17 @@ const ORG: &str = "test-org"; const ROOT_PURL: &str = "pkg:npm/root-dep@1.0.0"; const APP_PURL: &str = "pkg:npm/app-dep@1.0.0"; -/// A `scan` command with the full `SOCKET_*` environment scrubbed (except -/// the workspace-pinned `SOCKET_NO_CONFIG`) and the working directory -/// pinned — the `rollback_invariants.rs` recipe, so no test can be -/// satisfied (or broken) by ambient environment instead of its flags. +/// A `scan` command with the hermetic `SOCKET_*` environment and the +/// working directory pinned, so no test can be satisfied (or broken) by +/// ambient environment instead of its flags. /// `VIRTUAL_ENV` is scrubbed too: the python crawler honors it FIRST, so /// an activated venv would inject its site-packages into every scan and /// break the exact-batch-body oracles (see `in_process_scan.rs`). /// Telemetry is disabled so the request log holds ONLY patch-API traffic. fn scan_cmd(cwd: &Path) -> Command { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.arg("scan").current_dir(cwd); - for (key, _) in std::env::vars_os() { - if key.to_string_lossy().starts_with("SOCKET_") - && key.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(&key); - } - } - cmd.env_remove("VIRTUAL_ENV"); - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); + hermetic::scrub_extra(&mut cmd, &[Extra::Venv]); cmd } diff --git a/crates/socket-patch-cli/tests/scan/scan_vendor_step_error_e2e.rs b/crates/socket-patch-cli/tests/scan/scan_vendor_step_error_e2e.rs index a3969d7a0..49f911b2d 100644 --- a/crates/socket-patch-cli/tests/scan/scan_vendor_step_error_e2e.rs +++ b/crates/socket-patch-cli/tests/scan/scan_vendor_step_error_e2e.rs @@ -1,9 +1,8 @@ //! A failed artifact download preserves the vendor envelope and unrelated ledger entries. -use crate::common::{binary, git_sha256}; +use crate::common::{binary, git_sha256, hermetic_command}; use std::path::Path; -use std::process::Command; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -156,20 +155,13 @@ fn seed_unselected_ledger_entry(root: &Path) -> String { } fn run_cli(root: &Path, argv: &[&str]) -> (i32, String, String) { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic_command(&binary()); cmd.args(argv).current_dir(root); - // Scrub the ambient `SOCKET_*` surface (prefix scrub — fixed lists rot) - // so a developer's shell can't steer the child, then force the telemetry - // kill-switch: telemetry resolves its endpoint from env only, so an - // ambient value would ship this run's events to the LIVE API. - for (key, _) in std::env::vars_os() { - if key.to_string_lossy().starts_with("SOCKET_") - && key.to_string_lossy() != "SOCKET_NO_CONFIG" - { - cmd.env_remove(&key); - } - } - cmd.env("SOCKET_TELEMETRY_DISABLED", "1"); + // `hermetic_command` scrubs the ambient `SOCKET_*` surface so a + // developer's shell can't steer the child, and forces the telemetry + // kill-switch on: telemetry resolves its endpoint from env only (and + // the scrub keeps `SOCKET_TELEMETRY_*`), so a run with telemetry on + // would ship its events to whatever endpoint the shell names. let out = cmd.output().expect("run"); ( out.status.code().unwrap_or(-1), diff --git a/crates/socket-patch-cli/tests/spawn_env_hygiene.rs b/crates/socket-patch-cli/tests/spawn_env_hygiene.rs index d97b82670..fce596335 100644 --- a/crates/socket-patch-cli/tests/spawn_env_hygiene.rs +++ b/crates/socket-patch-cli/tests/spawn_env_hygiene.rs @@ -128,24 +128,10 @@ const PENDING_RAW_SPAWNS: &[&str] = &[ "mode_migration_pypi.rs", "remove/remove_network.rs", "remove_rollback_api_overrides.rs", - "repair/coverage_fix_repair_vendor_predelete.rs", - "repair/covgap_commands_repair.rs", - "repair/covgap_commands_repair_vendor.rs", - "repair/repair_invariants.rs", "rollback/cli_rollback_silent.rs", "rollback/rollback_duality_invariants.rs", "rollback/rollback_invariants.rs", "rollback/rollback_multicopy_blob_gate.rs", - "scan/coverage_fix_scan_discovery_corrupt_ledger.rs", - "scan/covgap_commands_fetch_stage.rs", - "scan/covgap_commands_scan_vendor_flow.rs", - "scan/hosted_management_refusals.rs", - "scan/scan_batch_sizing_e2e.rs", - "scan/scan_ecosystems_scope_e2e.rs", - "scan/scan_invariants.rs", - "scan/scan_ordered_concurrency_e2e.rs", - "scan/scan_paths_e2e.rs", - "scan/scan_vendor_step_error_e2e.rs", "scan_api_retry_e2e.rs", "scan_pnpm_relocated_store_cwd_e2e.rs", "scan_requirements_lock_only.rs", @@ -393,6 +379,7 @@ fn command_never_leaks_its_hostile_seeds() { "SOCKET_GLOBAL", "SOCKET_GLOBAL_PREFIX", "SOCKET_DRY_RUN", + "SOCKET_ECOSYSTEMS", "SOCKET_MANIFEST_PATH", "SOCKET_JSON", "SOCKET_SILENT",