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                });
60            }
61        }
62    }
63
64    findings
65}
66
67fn redact_value(val: &str) -> String {
68    if val.len() <= 20 {
69        val.to_string()
70    } else {
71        format!("{}...", &val[..20])
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn test_no_proxy() {
81        let env = TestEnv {
82            vars: std::collections::HashMap::new(),
83        };
84        let findings = check(&env);
85        assert!(findings.is_empty());
86    }
87
88    #[test]
89    fn test_http_proxy_set() {
90        let mut vars = std::collections::HashMap::new();
91        vars.insert(
92            "HTTP_PROXY".to_string(),
93            "http://proxy.corp:8080".to_string(),
94        );
95        let env = TestEnv { vars };
96        let findings = check(&env);
97        assert_eq!(findings.len(), 1);
98        assert_eq!(findings[0].rule_id, RuleId::ProxyEnvSet);
99    }
100}