diff --git a/crates/socket-patch-cli/tests/e2e_gem.rs b/crates/socket-patch-cli/tests/e2e_gem.rs index 9399b9949..3b6560f76 100644 --- a/crates/socket-patch-cli/tests/e2e_gem.rs +++ b/crates/socket-patch-cli/tests/e2e_gem.rs @@ -20,7 +20,7 @@ #[path = "common/mod.rs"] mod common; -use common::{binary, git_sha256}; +use common::{binary, git_sha256, hermetic}; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; @@ -60,20 +60,20 @@ fn git_sha256_file(path: &Path) -> String { git_sha256(&content) } +/// Run the binary with the hermetic `SOCKET_*` environment (no token, so the +/// scans take the public-proxy path `--proxy-url` pins). The gem crawler +/// reads `BUNDLE_PATH`, `BUNDLE_APP_CONFIG`, the user's `~/.bundle/config` +/// and the `gem env` homes (`GEM_HOME`, `GEM_PATH`, `~/.gem`), and the scans +/// are unscoped, so every `BUNDLE_*` var, both gem-home vars and an +/// activated venv are scrubbed and `HOME` points at a stand-in +/// (`jvm_env::isolate_cli`): any of them would add the developer's gems or +/// packages to the exact purl sets these tests assert. fn run(cwd: &Path, args: &[&str]) -> (i32, String, String) { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic::command(&binary()); + hermetic::scrub_extra(&mut cmd, &[hermetic::Extra::Bundle, hermetic::Extra::Venv]); + cmd.env_remove("GEM_HOME").env_remove("GEM_PATH"); + common::jvm_env::isolate_cli(&mut cmd); cmd.args(args).current_dir(cwd); - // Hermeticity, mirroring the production e2e suites: scrub every ambient - // `SOCKET_*` (token, api/proxy URL, org, legacy aliases — not just the - // two token vars) and block the socket-cli config.json, so a developer's - // login or configured apiBaseUrl cannot silently repoint or re-tier - // these runs. - for (k, _) in std::env::vars_os() { - if k.to_string_lossy().starts_with("SOCKET_") { - cmd.env_remove(&k); - } - } - cmd.env("SOCKET_NO_CONFIG", "true"); let out: Output = cmd.output().expect("failed to execute socket-patch binary"); let code = out.status.code().unwrap_or(-1); @@ -94,6 +94,11 @@ fn assert_run_ok(cwd: &Path, args: &[&str], context: &str) -> (String, String) { fn bundle_run(cwd: &Path, args: &[&str]) { let mut cmd = Command::new("bundle"); cmd.args(args).current_dir(cwd); + // Scrub first: the sweeps would otherwise remove the BUNDLE_PATH below, + // and an ambient `BUNDLE_*` (frozen, deployment, without, ...) would + // change what this install does. + hermetic::scrub_socket_vars(&mut cmd); + hermetic::scrub_extra(&mut cmd, &[hermetic::Extra::Bundle]); // Bundler 4 removed `bundle install --path`; BUNDLE_PATH is honored by // bundler 2 through 4 and keeps the vendor/bundle/ruby/*/gems layout // `find_gem_dir` expects. It also upholds cache_env's hermeticity diff --git a/crates/socket-patch-cli/tests/spawn_env_hygiene.rs b/crates/socket-patch-cli/tests/spawn_env_hygiene.rs index 3f77ada18..d2a76309c 100644 --- a/crates/socket-patch-cli/tests/spawn_env_hygiene.rs +++ b/crates/socket-patch-cli/tests/spawn_env_hygiene.rs @@ -30,7 +30,6 @@ use serial_test::serial; /// scrubs by hand. const PENDING_RAW_SPAWNS: &[&str] = &[ "apply/apply_network.rs", - "e2e_gem.rs", "e2e_hosted_production.rs", "e2e_maven.rs", "e2e_npm.rs", @@ -42,8 +41,6 @@ const PENDING_RAW_SPAWNS: &[&str] = &[ "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", ]; /// The environment `cmd`'s child would see: the parent environment with diff --git a/crates/socket-patch-cli/tests/vendor/vendor_gem_lockfile_only_e2e.rs b/crates/socket-patch-cli/tests/vendor/vendor_gem_lockfile_only_e2e.rs index 1c5f507a5..b7bf802a3 100644 --- a/crates/socket-patch-cli/tests/vendor/vendor_gem_lockfile_only_e2e.rs +++ b/crates/socket-patch-cli/tests/vendor/vendor_gem_lockfile_only_e2e.rs @@ -1,9 +1,8 @@ //! Service vendoring of portable gems without a local install or registry fetch. -use crate::common::{binary, git_sha256}; +use crate::common::{binary, git_sha256, hermetic}; use std::path::Path; -use std::process::Command; use wiremock::matchers::{method, path as wm_path}; use wiremock::{Mock, MockServer, ResponseTemplate}; @@ -126,11 +125,17 @@ async fn mount_gem_download(mock: &MockServer, gem: Vec) { } /// `vendor --json --vendor-source ` through the built binary, with -/// every ambient `SOCKET_*` var scrubbed and the API pointed at a +/// the hermetic `SOCKET_*` environment, every ambient `BUNDLE_*` var and +/// both gem-home vars scrubbed and `HOME` on a stand-in (an ambient +/// `BUNDLE_PATH`, app config, `~/.bundle/config` or gem home moves the gem +/// store the crawler reads) and the API pointed at a /// guaranteed-dead endpoint (patch staging is satisfied from /// `.socket/blobs`, so nothing should reach it). fn run_vendor(root: &Path, source: &str, api_url: &str) -> (i32, serde_json::Value, String) { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic::command(&binary()); + hermetic::scrub_extra(&mut cmd, &[hermetic::Extra::Bundle]); + cmd.env_remove("GEM_HOME").env_remove("GEM_PATH"); + crate::common::jvm_env::isolate_cli(&mut cmd); cmd.args([ "vendor", "--json", @@ -146,12 +151,6 @@ fn run_vendor(root: &Path, source: &str, api_url: &str) -> (i32, serde_json::Val "test-org", ]) .current_dir(root); - 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"); let fixture = crate::prebuilt_common::Server::project(root); fixture.command(&mut cmd); let out = cmd.output().expect("run socket-patch vendor"); diff --git a/crates/socket-patch-cli/tests/vendor/vendor_rerun_no_network_e2e.rs b/crates/socket-patch-cli/tests/vendor/vendor_rerun_no_network_e2e.rs index d8be5402c..be3e8aac8 100644 --- a/crates/socket-patch-cli/tests/vendor/vendor_rerun_no_network_e2e.rs +++ b/crates/socket-patch-cli/tests/vendor/vendor_rerun_no_network_e2e.rs @@ -19,10 +19,9 @@ //! Hermetic: every registry base and the patch API point at a //! guaranteed-dead local endpoint, and patch staging reads `.socket/blobs`. -use crate::common::{binary, git_sha256}; +use crate::common::{binary, git_sha256, hermetic}; use std::path::Path; -use std::process::Command; use sha2::{Digest, Sha256}; @@ -67,16 +66,20 @@ fn write_manifest(root: &Path, purl: &str, uuid: &str, file: &str, before: &[u8] std::fs::write(socket.join("blobs").join(git_sha256(after)), after).unwrap(); } -/// `vendor --json --vendor-source build` through the built binary with -/// every ambient `SOCKET_*` var scrubbed, the API and every registry base -/// pointed at `dead`, and `env` on top. +/// `vendor --json --vendor-source service` through the built binary with +/// the hermetic `SOCKET_*` environment, an activated venv, the gem homes +/// and every ambient `BUNDLE_*` var scrubbed and `HOME` on a stand-in (they +/// move the Python and gem stores the crawlers read), the API and every +/// registry base pointed at `dead`, and `env` on top. fn run_vendor( root: &Path, dead: &str, extra: &[&str], env: &[(&str, &str)], ) -> (i32, serde_json::Value, String) { - let mut cmd = Command::new(binary()); + let mut cmd = hermetic::command(&binary()); + hermetic::scrub_extra(&mut cmd, &[hermetic::Extra::Bundle, hermetic::Extra::Venv]); + crate::common::jvm_env::isolate_cli(&mut cmd); cmd.args([ "vendor", "--json", @@ -93,16 +96,10 @@ fn run_vendor( ]) .args(extra) .current_dir(root); - for (key, _) in std::env::vars() { - if key.starts_with("SOCKET_") && key != "SOCKET_NO_CONFIG" { - cmd.env_remove(key); - } - } - for var in ["VIRTUAL_ENV", "CONDA_PREFIX", "BUNDLE_PATH", "GEM_HOME"] { + for var in ["CONDA_PREFIX", "GEM_HOME", "GEM_PATH"] { cmd.env_remove(var); } - cmd.env("SOCKET_TELEMETRY_DISABLED", "1") - .env("SOCKET_CRATES_REGISTRY", dead) + cmd.env("SOCKET_CRATES_REGISTRY", dead) .env("SOCKET_GOPROXY", dead) .env("SOCKET_PYPI_JSON_API", dead) .env("SOCKET_NPM_REGISTRY", dead) @@ -850,7 +847,7 @@ async fn cargo_service_vendor_never_downloads_the_pristine_crate() { let empty_home = tmp.path().join("cargo-home"); std::fs::create_dir_all(&empty_home).unwrap(); - let mut cmd = Command::new(binary()); + let mut cmd = hermetic::command(&binary()); cmd.args([ "vendor", "--json", @@ -864,13 +861,7 @@ async fn cargo_service_vendor_never_downloads_the_pristine_crate() { "acme", ]) .current_dir(&root); - for (key, _) in std::env::vars() { - if key.starts_with("SOCKET_") && key != "SOCKET_NO_CONFIG" { - cmd.env_remove(key); - } - } let out = cmd - .env("SOCKET_TELEMETRY_DISABLED", "1") .env("SOCKET_CRATES_REGISTRY", registry.uri()) .env("CARGO_HOME", &empty_home) .output() @@ -975,7 +966,7 @@ async fn cargo_service_never_vendors_a_git_or_custom_registry_crate() { let empty_home = tmp.path().join("cargo-home"); std::fs::create_dir_all(&empty_home).unwrap(); - let mut cmd = Command::new(binary()); + let mut cmd = hermetic::command(&binary()); cmd.args([ "vendor", "--json", @@ -989,13 +980,7 @@ async fn cargo_service_never_vendors_a_git_or_custom_registry_crate() { "acme", ]) .current_dir(&root); - for (key, _) in std::env::vars() { - if key.starts_with("SOCKET_") && key != "SOCKET_NO_CONFIG" { - cmd.env_remove(key); - } - } let out = cmd - .env("SOCKET_TELEMETRY_DISABLED", "1") .env("SOCKET_CRATES_REGISTRY", registry.uri()) .env("CARGO_HOME", &empty_home) .output()