diff --git a/src/openhuman/config/ops.rs b/src/openhuman/config/ops.rs index 2e4cbe39c..88c50e8bb 100644 --- a/src/openhuman/config/ops.rs +++ b/src/openhuman/config/ops.rs @@ -818,17 +818,49 @@ pub fn get_runtime_flags() -> RpcOutcome { } /// Updates the `OPENHUMAN_BROWSER_ALLOW_ALL` environment flag. +/// +/// **Security note:** when enabled, this disables the browser tool's +/// per-domain allowlist for the entire process. Both transitions are +/// audit-logged at WARN level with a `[SECURITY]` prefix so operators +/// (and `journalctl -g '\[SECURITY\]'` style scrapes) can spot +/// allowlist toggles in the live log stream. +/// +/// `is_private_host` checks still apply to the resolved IP, so this +/// flag does not unlock loopback / RFC1918 destinations. pub fn set_browser_allow_all(enabled: bool) -> RpcOutcome { + let was_enabled = env_flag_enabled("OPENHUMAN_BROWSER_ALLOW_ALL"); if enabled { std::env::set_var("OPENHUMAN_BROWSER_ALLOW_ALL", "1"); } else { std::env::remove_var("OPENHUMAN_BROWSER_ALLOW_ALL"); } + let now_enabled = env_flag_enabled("OPENHUMAN_BROWSER_ALLOW_ALL"); let flags = RuntimeFlagsOut { - browser_allow_all: env_flag_enabled("OPENHUMAN_BROWSER_ALLOW_ALL"), + browser_allow_all: now_enabled, log_prompts: env_flag_enabled("OPENHUMAN_LOG_PROMPTS"), }; - RpcOutcome::single_log(flags, "browser allow-all flag updated") + + if was_enabled != now_enabled { + if now_enabled { + tracing::warn!( + "[SECURITY] browser allow-all enabled via RPC: \ + per-domain allowlist is now bypassed for all sessions \ + (private-host check still applies)" + ); + } else { + tracing::info!( + "[SECURITY] browser allow-all disabled via RPC: \ + per-domain allowlist re-enforced" + ); + } + } + + let log_msg = if now_enabled { + "[SECURITY] browser allow-all flag set to enabled" + } else { + "[SECURITY] browser allow-all flag set to disabled" + }; + RpcOutcome::single_log(flags, log_msg) } /// Checks if a specific onboarding flag file exists in the workspace. diff --git a/src/openhuman/config/ops_tests.rs b/src/openhuman/config/ops_tests.rs index 2d4bf62dc..46f1f93e3 100644 --- a/src/openhuman/config/ops_tests.rs +++ b/src/openhuman/config/ops_tests.rs @@ -167,6 +167,45 @@ fn set_browser_allow_all_toggles_env_var() { } } +#[test] +fn set_browser_allow_all_emits_security_audit_log() { + let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); + let before = std::env::var("OPENHUMAN_BROWSER_ALLOW_ALL").ok(); + + let enable_outcome = set_browser_allow_all(true); + assert_eq!(enable_outcome.logs.len(), 1); + let enable_log = &enable_outcome.logs[0]; + assert!( + enable_log.contains("[SECURITY]"), + "enable log should be audit-tagged: {enable_log}" + ); + assert!( + enable_log.contains("enabled"), + "enable log should mention enabled state: {enable_log}" + ); + assert!(enable_outcome.value.browser_allow_all); + + let disable_outcome = set_browser_allow_all(false); + assert_eq!(disable_outcome.logs.len(), 1); + let disable_log = &disable_outcome.logs[0]; + assert!( + disable_log.contains("[SECURITY]"), + "disable log should be audit-tagged: {disable_log}" + ); + assert!( + disable_log.contains("disabled"), + "disable log should mention disabled state: {disable_log}" + ); + assert!(!disable_outcome.value.browser_allow_all); + + unsafe { + match before { + Some(v) => std::env::set_var("OPENHUMAN_BROWSER_ALLOW_ALL", v), + None => std::env::remove_var("OPENHUMAN_BROWSER_ALLOW_ALL"), + } + } +} + // ── snapshot_config_json ─────────────────────────────────────── #[test]