Skip to main content

yt/
redact.rs

1use std::sync::{LazyLock, RwLock};
2
3static SECRETS: LazyLock<RwLock<Vec<String>>> = LazyLock::new(|| RwLock::new(Vec::new()));
4
5/// Remembers a secret so that `redact` hides it wherever it appears.
6pub fn register(secret: &str) {
7    if secret.len() < 8 {
8        return;
9    }
10    let mut secrets = SECRETS
11        .write()
12        .unwrap_or_else(|poisoned| poisoned.into_inner());
13    if !secrets.iter().any(|known| known == secret) {
14        secrets.push(secret.to_string());
15    }
16}
17
18pub fn redact(text: &str) -> String {
19    let secrets = SECRETS
20        .read()
21        .unwrap_or_else(|poisoned| poisoned.into_inner());
22    secrets.iter().fold(text.to_string(), |text, secret| {
23        text.replace(secret.as_str(), "[REDACTED]")
24    })
25}
26
27/// Enough of a token to recognise it, never enough to use it.
28pub fn mask(token: &str) -> String {
29    let chars: Vec<char> = token.chars().collect();
30    if chars.len() <= 12 {
31        return "****".to_string();
32    }
33    let head: String = chars[..4].iter().collect();
34    let tail: String = chars[chars.len() - 4..].iter().collect();
35    format!("{head}…{tail}")
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn registered_secrets_are_redacted() {
44        register("perm-c2VjcmV0.dGVzdA==.abcdef123456");
45        let text = redact("Bearer perm-c2VjcmV0.dGVzdA==.abcdef123456 failed");
46        assert_eq!(text, "Bearer [REDACTED] failed");
47    }
48
49    #[test]
50    fn short_strings_are_not_registered() {
51        register("abc");
52        assert_eq!(redact("abc"), "abc");
53    }
54
55    #[test]
56    fn mask_keeps_four_characters_at_each_end() {
57        assert_eq!(mask("perm-c2VjcmV0.abcdef123456"), "perm…3456");
58        assert_eq!(mask("short"), "****");
59    }
60}