use std::collections::BTreeMap;
use tokio::process::Command;
use super::capabilities::{AddonCapabilities, BASE_ENV_ALLOWLIST};
pub fn apply_env(
cmd: &mut Command,
declared_env: &BTreeMap<String, String>,
capabilities: Option<&AddonCapabilities>,
) {
if let Some(caps) = capabilities {
cmd.env_clear();
for key in BASE_ENV_ALLOWLIST {
if let Ok(val) = std::env::var(key) {
cmd.env(key, val);
}
}
for name in &caps.env {
if let Ok(val) = std::env::var(name) {
cmd.env(name, val);
}
}
}
for (k, v) in declared_env {
cmd.env(k, v);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::addons::capabilities::NetworkAccess;
fn env_keys(cmd: &Command) -> Vec<String> {
cmd.as_std()
.get_envs()
.filter_map(|(k, v)| v.map(|_| k.to_string_lossy().into_owned()))
.collect()
}
#[test]
fn declared_capabilities_scrub_host_secret() {
crate::test_env::set_var("LEAN_CTX_ADDON_TEST_SECRET", "top-secret");
let caps = AddonCapabilities::default();
let mut cmd = Command::new("true");
apply_env(&mut cmd, &BTreeMap::new(), Some(&caps));
let keys = env_keys(&cmd);
crate::test_env::remove_var("LEAN_CTX_ADDON_TEST_SECRET");
assert!(
!keys.iter().any(|k| k == "LEAN_CTX_ADDON_TEST_SECRET"),
"scrubbed child must not see the host secret"
);
}
#[test]
fn declared_env_name_passes_through() {
crate::test_env::set_var("LEAN_CTX_ADDON_TEST_ALLOWED", "visible");
let caps = AddonCapabilities {
network: NetworkAccess::Full,
env: vec!["LEAN_CTX_ADDON_TEST_ALLOWED".into()],
..Default::default()
};
let mut cmd = Command::new("true");
apply_env(&mut cmd, &BTreeMap::new(), Some(&caps));
let keys = env_keys(&cmd);
crate::test_env::remove_var("LEAN_CTX_ADDON_TEST_ALLOWED");
assert!(
keys.iter().any(|k| k == "LEAN_CTX_ADDON_TEST_ALLOWED"),
"declared env name must reach the child"
);
}
#[test]
fn mcp_env_values_are_always_set() {
let mut declared = BTreeMap::new();
declared.insert("ADDON_MODE".to_string(), "serve".to_string());
let caps = AddonCapabilities::default();
let mut cmd = Command::new("true");
apply_env(&mut cmd, &declared, Some(&caps));
assert!(env_keys(&cmd).iter().any(|k| k == "ADDON_MODE"));
}
#[test]
fn legacy_path_does_not_clear_env() {
let mut declared = BTreeMap::new();
declared.insert("ADDON_MODE".to_string(), "serve".to_string());
let mut cmd = Command::new("true");
apply_env(&mut cmd, &declared, None);
assert!(env_keys(&cmd).iter().any(|k| k == "ADDON_MODE"));
}
}