Skip to main content

tirith_core/rules/
environment.rs

1use crate::verdict::{Evidence, Finding, RuleId, Severity};
2
3/// Trait for environment variable access (enables deterministic testing).
4pub trait EnvSnapshot {
5    fn get(&self, key: &str) -> Option<String>;
6}
7
8/// Real environment implementation.
9pub struct RealEnv;
10
11impl EnvSnapshot for RealEnv {
12    fn get(&self, key: &str) -> Option<String> {
13        std::env::var(key).ok()
14    }
15}
16
17/// Test environment implementation.
18#[cfg(test)]
19pub struct TestEnv {
20    pub vars: std::collections::HashMap<String, String>,
21}
22
23#[cfg(test)]
24impl EnvSnapshot for TestEnv {
25    fn get(&self, key: &str) -> Option<String> {
26        self.vars.get(key).cloned()
27    }
28}
29
30/// Check environment variables for proxy settings.
31pub fn check(env: &dyn EnvSnapshot) -> Vec<Finding> {
32    let mut findings = Vec::new();
33
34    let proxy_vars = [
35        "HTTP_PROXY",
36        "http_proxy",
37        "HTTPS_PROXY",
38        "https_proxy",
39        "ALL_PROXY",
40        "all_proxy",
41    ];
42
43    for var in &proxy_vars {
44        if let Some(val) = env.get(var) {
45            if !val.is_empty() {
46                findings.push(Finding {
47                    rule_id: RuleId::ProxyEnvSet,
48                    severity: Severity::Low,
49                    title: format!("Proxy environment variable {var} is set"),
50                    description: format!(
51                        "Environment variable {} is set to '{}'. Traffic may be intercepted by the proxy.",
52                        var,
53                        redact_value(&val)
54                    ),
55                    evidence: vec![Evidence::EnvVar {
56                        name: var.to_string(),
57                        value_preview: redact_value(&val),
58                    }],
59                    human_view: None,
60                    agent_view: None,
61                mitre_id: None,
62                custom_rule_id: None,
63                });
64            }
65        }
66    }
67
68    findings
69}
70
71fn redact_value(val: &str) -> String {
72    let prefix = crate::util::truncate_bytes(val, 20);
73    if prefix.len() == val.len() {
74        val.to_string()
75    } else {
76        format!("{prefix}...")
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_no_proxy() {
86        let env = TestEnv {
87            vars: std::collections::HashMap::new(),
88        };
89        let findings = check(&env);
90        assert!(findings.is_empty());
91    }
92
93    #[test]
94    fn test_http_proxy_set() {
95        let mut vars = std::collections::HashMap::new();
96        vars.insert(
97            "HTTP_PROXY".to_string(),
98            "http://proxy.corp:8080".to_string(),
99        );
100        let env = TestEnv { vars };
101        let findings = check(&env);
102        assert_eq!(findings.len(), 1);
103        assert_eq!(findings[0].rule_id, RuleId::ProxyEnvSet);
104    }
105}