Skip to main content

soma_mcp_client/security/
env.rs

1use std::collections::BTreeMap;
2
3use thiserror::Error;
4
5use crate::security::redact::redact_stdio_args;
6
7#[derive(Debug, Error, PartialEq, Eq)]
8pub enum EnvPolicyError {
9    #[error("environment variable name is invalid")]
10    InvalidName,
11    #[error("environment variable is protected")]
12    ProtectedName,
13    #[error("argument tries to weaken spawn guard")]
14    SpawnGuardOverride,
15}
16
17pub fn validate_env_name(name: &str) -> Result<(), EnvPolicyError> {
18    let mut chars = name.chars();
19    let Some(first) = chars.next() else {
20        return Err(EnvPolicyError::InvalidName);
21    };
22    if !(first == '_' || first.is_ascii_uppercase()) {
23        return Err(EnvPolicyError::InvalidName);
24    }
25    if !chars.all(|ch| ch == '_' || ch.is_ascii_uppercase() || ch.is_ascii_digit()) {
26        return Err(EnvPolicyError::InvalidName);
27    }
28    if matches!(name, "LD_PRELOAD" | "DYLD_INSERT_LIBRARIES") || name.starts_with("MCP_GATEWAY_") {
29        return Err(EnvPolicyError::ProtectedName);
30    }
31    Ok(())
32}
33
34pub fn validate_spawn_env(env: &BTreeMap<String, String>) -> Result<(), EnvPolicyError> {
35    for key in env.keys() {
36        validate_env_name(key)?;
37    }
38    Ok(())
39}
40
41pub fn reject_spawn_guard_overrides(args: &[String]) -> Result<(), EnvPolicyError> {
42    let joined = args.join(" ");
43    if joined.contains("disable_spawn_guard") || joined.contains("disable-spawn-guard") {
44        return Err(EnvPolicyError::SpawnGuardOverride);
45    }
46    Ok(())
47}
48
49#[must_use]
50pub fn redact_spawn_args_for_log(args: &[String]) -> Vec<String> {
51    redact_stdio_args(args)
52}
53
54#[cfg(test)]
55#[path = "env_tests.rs"]
56mod tests;