Skip to main content

safe_shell_scanner/
entropy.rs

1/// Calculate Shannon entropy of a string.
2/// High entropy (> 4.5) often indicates random/secret values.
3pub fn shannon_entropy(s: &str) -> f64 {
4    if s.is_empty() {
5        return 0.0;
6    }
7
8    let len = s.len() as f64;
9    let mut freq = [0u32; 256];
10
11    for &b in s.as_bytes() {
12        freq[b as usize] += 1;
13    }
14
15    freq.iter()
16        .filter(|&&count| count > 0)
17        .map(|&count| {
18            let p = count as f64 / len;
19            -p * p.log2()
20        })
21        .sum()
22}
23
24/// Returns true if the string looks like a high-entropy secret.
25/// Requires minimum 16 chars and entropy > 4.5 bits/char.
26pub fn is_high_entropy(s: &str) -> bool {
27    s.len() >= 16 && shannon_entropy(s) > 4.5
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33
34    #[test]
35    fn empty_string() {
36        assert_eq!(shannon_entropy(""), 0.0);
37    }
38
39    #[test]
40    fn single_char_repeated() {
41        assert_eq!(shannon_entropy("aaaaaaaaaaaaaaaa"), 0.0);
42    }
43
44    #[test]
45    fn low_entropy_string() {
46        // Repetitive string — low entropy
47        assert!(!is_high_entropy("aaaaaaaaaaaaaaaa"));
48        assert!(!is_high_entropy("aabbccddaabbccdd"));
49    }
50
51    #[test]
52    fn high_entropy_string() {
53        // Random base64-like string
54        assert!(is_high_entropy("aB3dE5fG7hI9jK1lM3nO5pQ7rS9tU1v"));
55    }
56
57    #[test]
58    fn too_short_even_if_random() {
59        assert!(!is_high_entropy("aB3dE5f"));
60    }
61
62    #[test]
63    fn real_api_key_like() {
64        assert!(is_high_entropy("sk_live_4eC39HqLyjWDarjtT1zdp7dc"));
65    }
66
67    #[test]
68    fn normal_path_is_low_entropy() {
69        assert!(!is_high_entropy("/usr/local/bin/node"));
70    }
71
72    #[test]
73    fn entropy_increases_with_diversity() {
74        let low = shannon_entropy("aaaa");
75        let high = shannon_entropy("abcd");
76        assert!(high > low);
77    }
78}