Skip to main content

edda_conductor/check/
mod.rs

1pub mod cmd_succeeds;
2pub mod edda_event;
3pub mod engine;
4pub mod file_contains;
5pub mod file_exists;
6pub mod git_clean;
7pub mod wait_until;
8
9use std::time::Duration;
10
11/// Output from a single check execution.
12#[derive(Debug, Clone)]
13pub struct CheckOutput {
14    pub passed: bool,
15    pub detail: Option<String>,
16    pub duration: Duration,
17}
18
19impl CheckOutput {
20    pub fn passed(duration: Duration) -> Self {
21        Self {
22            passed: true,
23            detail: None,
24            duration,
25        }
26    }
27
28    pub fn passed_with_detail(detail: String, duration: Duration) -> Self {
29        Self {
30            passed: true,
31            detail: Some(detail),
32            duration,
33        }
34    }
35
36    pub fn failed(detail: String, duration: Duration) -> Self {
37        Self {
38            passed: false,
39            detail: Some(detail),
40            duration,
41        }
42    }
43}
44
45/// Mask secrets in output strings before storing.
46pub fn mask_secrets(text: &str) -> String {
47    let patterns = [
48        // API keys
49        (r"sk-[a-zA-Z0-9]{20,}", "[MASKED]"),
50        (r"pk-[a-zA-Z0-9]{20,}", "[MASKED]"),
51        // Bearer tokens
52        (r"Bearer\s+[a-zA-Z0-9._\-]+", "Bearer [MASKED]"),
53        // key=value patterns
54        (
55            r"(?i)(password|secret|token|key|api_key|apikey)=[^\s&]+",
56            "$1=[MASKED]",
57        ),
58    ];
59
60    let mut result = text.to_string();
61    for (pattern, replacement) in patterns {
62        if let Ok(re) = regex::Regex::new(pattern) {
63            result = re.replace_all(&result, replacement).into_owned();
64        }
65    }
66    result
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn mask_api_keys() {
75        let input = "Error: sk-ant1234567890abcdefghij is invalid";
76        let masked = mask_secrets(input);
77        assert!(masked.contains("[MASKED]"));
78        assert!(!masked.contains("sk-ant"));
79    }
80
81    #[test]
82    fn mask_bearer_tokens() {
83        let input = "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.test";
84        let masked = mask_secrets(input);
85        assert!(masked.contains("Bearer [MASKED]"));
86    }
87
88    #[test]
89    fn mask_key_value() {
90        let input = "password=hunter2&token=abc123";
91        let masked = mask_secrets(input);
92        assert!(masked.contains("password=[MASKED]"));
93        assert!(masked.contains("token=[MASKED]"));
94    }
95
96    #[test]
97    fn no_mask_normal_text() {
98        let input = "cargo test --workspace passed";
99        assert_eq!(mask_secrets(input), input);
100    }
101}