soma_mcp_client/security/
env.rs1use 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;