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