diff --git a/charts/openab/templates/NOTES.txt b/charts/openab/templates/NOTES.txt index b18488d7..82213b5c 100644 --- a/charts/openab/templates/NOTES.txt +++ b/charts/openab/templates/NOTES.txt @@ -48,3 +48,7 @@ Agents deployed: kubectl rollout restart deployment/{{ include "openab.agentFullname" (dict "ctx" $ "agent" $name) }} {{- end }} {{- end }} + +⚠️ SECURITY: The agent subprocess only receives HOME, PATH, and explicitly configured [agent].env vars. + Any env var passed via [agent].env is accessible to the agent and could be exfiltrated via prompt injection. + All supported backends use OAuth/file-based auth — avoid passing API keys via env vars when possible. diff --git a/config.toml.example b/config.toml.example index 4c4a37fc..a9ad9271 100644 --- a/config.toml.example +++ b/config.toml.example @@ -54,6 +54,10 @@ working_dir = "/home/agent" # command = "claude" # args = ["--acp"] # working_dir = "/home/agent" +# ⚠️ SECURITY WARNING: Any env var listed here is accessible to the agent. +# A user could trick the agent into leaking these values via prompt injection. +# All supported backends support OAuth login — prefer that over env var API keys. +# Note: env vars here can override baseline vars (HOME, PATH, USER) if needed. # env = { ANTHROPIC_API_KEY = "${ANTHROPIC_API_KEY}" } # [agent] diff --git a/src/acp/connection.rs b/src/acp/connection.rs index 9c556a86..aebe8525 100644 --- a/src/acp/connection.rs +++ b/src/acp/connection.rs @@ -154,9 +154,37 @@ impl AcpConnection { { cmd.creation_flags(0x00000200); // CREATE_NEW_PROCESS_GROUP } + // Clear inherited env to prevent credential leakage (e.g. DISCORD_BOT_TOKEN). + // Only [agent].env values + essential baseline vars are passed through. + cmd.env_clear(); + // Preserve the real HOME so agents can find OAuth/auth files (~/.codex, + // ~/.claude, ~/.config/gh, etc.). working_dir is already set via + // current_dir() above and is not necessarily the user's home directory. + cmd.env("HOME", std::env::var("HOME").unwrap_or_else(|_| working_dir.into())); + cmd.env("PATH", std::env::var("PATH").unwrap_or_else(|_| "/usr/local/bin:/usr/bin:/bin".into())); + #[cfg(unix)] + { + cmd.env("USER", std::env::var("USER").unwrap_or_else(|_| "agent".into())); + } + #[cfg(windows)] + { + // Windows requires SystemRoot for DLL loading and basic OS functionality. + // USERPROFILE is the Windows equivalent of HOME. + cmd.env("USERPROFILE", std::env::var("USERPROFILE").unwrap_or_else(|_| working_dir.into())); + cmd.env("USERNAME", std::env::var("USERNAME").unwrap_or_else(|_| "agent".into())); + if let Ok(v) = std::env::var("SystemRoot") { cmd.env("SystemRoot", v); } + if let Ok(v) = std::env::var("SystemDrive") { cmd.env("SystemDrive", v); } + } for (k, v) in env { cmd.env(k, expand_env(v)); } + if !env.is_empty() { + let keys: Vec<&String> = env.keys().collect(); + tracing::warn!( + ?keys, + "[agent].env is set -- these values are accessible to the agent and could be exfiltrated via prompt injection" + ); + } let mut proc = cmd .spawn() .map_err(|e| anyhow!("failed to spawn {command}: {e}"))?;