diff --git a/crates/capsem-core/src/credential_broker/tests.rs b/crates/capsem-core/src/credential_broker/tests.rs index 1a5b0bb4d..6aec8dde8 100644 --- a/crates/capsem-core/src/credential_broker/tests.rs +++ b/crates/capsem-core/src/credential_broker/tests.rs @@ -13,7 +13,12 @@ struct EnvGuard { } impl EnvGuard { - fn install(capsem_home: &std::path::Path, home: &std::path::Path, test_store: &std::path::Path) -> Self { + fn install( + _lock: &tokio::sync::MutexGuard<'_, ()>, + capsem_home: &std::path::Path, + home: &std::path::Path, + test_store: &std::path::Path, + ) -> Self { CredentialStore::global().clear_for_test(); let old_home = std::env::var("HOME").ok(); let old_store = std::env::var(STORE_PATH_ENV).ok(); @@ -333,7 +338,7 @@ fn brokered_reference_does_not_leak_secret_to_unbound_domain() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); // Seed a real Anthropic secret and get its opaque broker reference. let obs = CredentialObservation { @@ -377,7 +382,7 @@ fn brokered_reference_substitutes_for_matching_provider_domain() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let obs = CredentialObservation { provider: CredentialProvider::Anthropic, @@ -405,7 +410,7 @@ fn broker_stores_secret_without_writing_user_settings() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let obs = CredentialObservation { provider: CredentialProvider::Github, @@ -443,7 +448,7 @@ fn duplicate_capture_is_memory_fast_and_does_not_rewrite_durable_store() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let obs = CredentialObservation { provider: CredentialProvider::OpenAi, @@ -476,7 +481,7 @@ fn replay_status_is_memory_only_and_hydration_is_explicit() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let empty_status = credential_store_status(); assert_eq!(empty_status.backend, "disk_override"); @@ -527,7 +532,7 @@ fn substitution_resolution_rehydrates_runtime_cache_on_real_use() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let obs = CredentialObservation { provider: CredentialProvider::OpenAi, @@ -558,7 +563,7 @@ fn broker_test_store_preserves_concurrent_captures() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let observations: Vec<_> = (0..64) .map(|index| CredentialObservation { @@ -601,7 +606,7 @@ fn replay_availability_requires_resolvable_broker_secret() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let missing = credential_reference("google", "not-stored"); assert!(!broker_reference_replay_available(Some("google"), &missing)); diff --git a/crates/capsem-core/src/credential_broker/tests/host_binding.rs b/crates/capsem-core/src/credential_broker/tests/host_binding.rs index d8bdba4e7..53c60e246 100644 --- a/crates/capsem-core/src/credential_broker/tests/host_binding.rs +++ b/crates/capsem-core/src/credential_broker/tests/host_binding.rs @@ -110,7 +110,7 @@ fn brokered_reference_is_never_dereferenced_for_lookalike_domain() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let secret = "sk-openai-real-key-do-not-leak"; let reference = seed(CredentialProvider::OpenAi, secret); @@ -148,7 +148,7 @@ fn injection_ledger_does_not_attribute_lookalike_domains_to_a_provider() { let dir = tempfile::tempdir().unwrap(); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let reference = seed(CredentialProvider::Anthropic, "sk-ant-ledger-secret"); let found = detect_brokered_http_references("evil-anthropic.com", None, &bearer(&reference), None, None); diff --git a/crates/capsem-core/src/fs_monitor/tests.rs b/crates/capsem-core/src/fs_monitor/tests.rs index 3a0775399..cee0c2ed5 100644 --- a/crates/capsem-core/src/fs_monitor/tests.rs +++ b/crates/capsem-core/src/fs_monitor/tests.rs @@ -11,7 +11,12 @@ struct EnvGuard { } impl EnvGuard { - fn install(capsem_home: &std::path::Path, home: &std::path::Path, test_store: &std::path::Path) -> Self { + fn install( + _lock: &tokio::sync::MutexGuard<'_, ()>, + capsem_home: &std::path::Path, + home: &std::path::Path, + test_store: &std::path::Path, + ) -> Self { let old_home = std::env::var("HOME").ok(); let old_store = std::env::var(crate::credential_broker::STORE_PATH_ENV).ok(); std::env::set_var("HOME", home); @@ -334,7 +339,7 @@ async fn emit_brokers_env_credentials_and_persists_reference() { let env_path = dir.path().join(".env"); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); std::fs::write(&env_path, "OPENAI_API_KEY=sk-env-secret\n").unwrap(); let db = DbWriter::open(&db_path, 64).unwrap(); @@ -585,7 +590,7 @@ async fn env_symlink_to_a_host_secret_is_never_read_or_brokered() { let db_path = dir.path().join("session.db"); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let host_secret = dir.path().join("host-credentials"); std::fs::write(&host_secret, "AWS_SECRET_ACCESS_KEY=sk-host-only-secret\n").unwrap(); @@ -630,7 +635,7 @@ async fn env_symlink_is_refused_by_the_open_even_if_it_claims_to_be_a_file() { let db_path = dir.path().join("session.db"); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let host_secret = dir.path().join("host-credentials"); std::fs::write(&host_secret, "AWS_SECRET_ACCESS_KEY=sk-host-only-secret\n").unwrap(); @@ -871,6 +876,7 @@ async fn overflow_defers_events_to_the_next_scan_and_records_a_marker() { /// workspace wherever the guest moved it. #[tokio::test] async fn a_workspace_swapped_for_a_host_link_is_never_walked_or_read() { + let _lock = crate::credential_broker::TEST_ENV_LOCK.lock().await; let dir = tempfile::tempdir().unwrap(); let share = dir.path().join("guest"); let workspace = share.join("workspace"); @@ -894,7 +900,7 @@ async fn a_workspace_swapped_for_a_host_link_is_never_walked_or_read() { let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let db = DbWriter::open(&dir.path().join("session.db"), 64).unwrap(); let brokered = FsMonitor::broker_env_file_credentials( &EmitContext { diff --git a/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests.rs b/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests.rs index 029b4ac98..07e9718fe 100644 --- a/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests.rs +++ b/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests.rs @@ -62,38 +62,26 @@ fn any_conn() -> ConnMeta { struct EnvGuard { // Redirects CAPSEM_HOME/RUN_DIR/ASSETS_DIR together; restores on drop. - // None for trace-only guards, which redirect no paths. - _capsem_paths: Option, + _capsem_paths: capsem_foundation::paths::CapsemPathsGuard, old_home: Option, old_store: Option, - old_trace: Option, } impl EnvGuard { - fn install(capsem_home: &std::path::Path, home: &std::path::Path, test_store: &std::path::Path) -> Self { + fn install( + _lock: &tokio::sync::MutexGuard<'_, ()>, + capsem_home: &std::path::Path, + home: &std::path::Path, + test_store: &std::path::Path, + ) -> Self { let old_home = std::env::var("HOME").ok(); let old_store = std::env::var(crate::credential_broker::STORE_PATH_ENV).ok(); - let old_trace = std::env::var("CAPSEM_TRACE_ID").ok(); std::env::set_var("HOME", home); std::env::set_var(crate::credential_broker::STORE_PATH_ENV, test_store); Self { - _capsem_paths: Some(capsem_foundation::paths::CapsemPathsGuard::redirect(capsem_home)), - old_home, - old_store, - old_trace, - } - } - - fn trace_only(trace_id: &str) -> Self { - let old_home = std::env::var("HOME").ok(); - let old_store = std::env::var(crate::credential_broker::STORE_PATH_ENV).ok(); - let old_trace = std::env::var("CAPSEM_TRACE_ID").ok(); - std::env::set_var("CAPSEM_TRACE_ID", trace_id); - Self { - _capsem_paths: None, + _capsem_paths: capsem_foundation::paths::CapsemPathsGuard::redirect(capsem_home), old_home, old_store, - old_trace, } } } @@ -108,6 +96,23 @@ impl Drop for EnvGuard { Some(v) => std::env::set_var(crate::credential_broker::STORE_PATH_ENV, v), None => std::env::remove_var(crate::credential_broker::STORE_PATH_ENV), } + } +} + +struct TraceEnvGuard { + old_trace: Option, +} + +impl TraceEnvGuard { + fn install(_lock: &tokio::sync::MutexGuard<'_, ()>, trace_id: &str) -> Self { + let old_trace = std::env::var("CAPSEM_TRACE_ID").ok(); + std::env::set_var("CAPSEM_TRACE_ID", trace_id); + Self { old_trace } + } +} + +impl Drop for TraceEnvGuard { + fn drop(&mut self) { match &self.old_trace { Some(v) => std::env::set_var("CAPSEM_TRACE_ID", v), None => std::env::remove_var("CAPSEM_TRACE_ID"), @@ -373,7 +378,8 @@ fn agy_google_tool_call_survives_into_ledger_counters() { #[test] fn openai_non_streaming_tool_call_carries_request_trace() { - let _trace_guard = EnvGuard::trace_only("feedfacecafebeef"); + let _lock = crate::credential_broker::TEST_ENV_LOCK.blocking_lock(); + let _trace_guard = TraceEnvGuard::install(&_lock, "feedfacecafebeef"); let mut req_ctx = anthropic_req_ctx(); req_ctx.domain = "127.0.0.1".into(); req_ctx.ai_provider = Some(ProviderKind::OpenAi); diff --git a/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests/headers.rs b/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests/headers.rs index e37e2ab50..489c4bdde 100644 --- a/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests/headers.rs +++ b/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests/headers.rs @@ -14,6 +14,7 @@ async fn an_observed_credential_is_redacted_from_every_stored_header_and_body() let dir = tempfile::tempdir().unwrap(); let db_path = dir.path().join("session.db"); let _guard = EnvGuard::install( + &_lock, &dir.path().join("capsem-home"), dir.path(), &dir.path().join("credential-store.json"), diff --git a/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests/ledger.rs b/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests/ledger.rs index e0e73c42b..60580e652 100644 --- a/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests/ledger.rs +++ b/crates/capsem-core/src/net/mitm_proxy/telemetry_hook/tests/ledger.rs @@ -12,7 +12,7 @@ async fn hook_writes_substitution_event_and_shared_credential_ref() { let db_path = dir.path().join("session.db"); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let db = Arc::new(DbWriter::open(&db_path, 64).expect("test db")); let deps = Arc::new(TelemetryDeps { @@ -87,7 +87,7 @@ async fn hook_does_not_repay_capture_ledger_for_repeated_identical_credential() let db_path = dir.path().join("session.db"); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let db = Arc::new(DbWriter::open(&db_path, 64).expect("test db")); let deps = Arc::new(TelemetryDeps { @@ -309,7 +309,7 @@ async fn hook_writes_injected_substitution_event_for_broker_ref_replay() { let db_path = dir.path().join("session.db"); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let db = Arc::new(DbWriter::open(&db_path, 64).expect("test db")); let deps = Arc::new(TelemetryDeps { @@ -379,7 +379,7 @@ async fn hook_detects_response_body_token_exchange_and_redacts_preview() { let db_path = dir.path().join("session.db"); let capsem_home = dir.path().join("capsem-home"); let test_store = dir.path().join("credential-store.json"); - let _guard = EnvGuard::install(&capsem_home, dir.path(), &test_store); + let _guard = EnvGuard::install(&_lock, &capsem_home, dir.path(), &test_store); let db = Arc::new(DbWriter::open(&db_path, 64).expect("test db")); let deps = Arc::new(TelemetryDeps {