use tokio::process::Command;
const INHERITED: &[&str] = &[
"PATH",
"HOME",
"TMPDIR",
"SHELL",
"USER",
"LOGNAME",
"LANG",
"LC_ALL",
"TERM",
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
"XDG_CACHE_HOME",
"XDG_STATE_HOME",
"HTTP_PROXY",
"HTTPS_PROXY",
"NO_PROXY",
"http_proxy",
"https_proxy",
"no_proxy",
"SSL_CERT_FILE",
"SSL_CERT_DIR",
"NODE_EXTRA_CA_CERTS",
"TOKENIZERS_PARALLELISM",
];
const MODEL_ACCOUNT: &[&str] = &[
"ANTHROPIC_AUTH_TOKEN",
"CLAUDE_CODE_OAUTH_TOKEN",
"ANTHROPIC_API_KEY",
"CODEX_API_KEY",
"OPENAI_API_KEY",
"LYS_ACCOUNT_VARIABLE",
"ANTHROPIC_BASE_URL",
"OPENAI_BASE_URL",
];
pub(super) fn validate(names: &[String]) -> anyhow::Result<()> {
for name in names {
let upper = name.to_ascii_uppercase();
anyhow::ensure!(
!name.is_empty()
&& name.len() <= 128
&& name
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_'),
"environment_name_invalid"
);
anyhow::ensure!(
!["KEY", "TOKEN", "SECRET", "PASSWORD", "CREDENTIAL"]
.iter()
.any(|part| upper.contains(part))
&& !["_AUTH", "_PASS", "_PAT", "DATABASE_URL"]
.iter()
.any(|part| upper.ends_with(part)),
"credential_environment_refused: {name}"
);
}
Ok(())
}
pub(super) fn apply(command: &mut Command, declared: &[String]) -> anyhow::Result<()> {
validate(declared)?;
command.env_clear();
for name in INHERITED.iter().chain(MODEL_ACCOUNT) {
if let Some(value) = std::env::var_os(name) {
command.env(name, value);
}
}
for name in declared {
let value = std::env::var_os(name)
.ok_or_else(|| anyhow::anyhow!("environment_declared_but_absent: {name}"))?;
command.env(name, value);
}
command.env_remove("LYS_AGENT_PASS");
Ok(())
}