tirith_core/rules/
environment.rs1use crate::verdict::{Evidence, Finding, RuleId, Severity};
2
3pub trait EnvSnapshot {
5 fn get(&self, key: &str) -> Option<String>;
6}
7
8pub 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#[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
30pub 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}