Skip to main content

specfold_core/guards/
secrets.rs

1//! Built-in secret patterns. Deliberately narrow: each one is a format with a fixed prefix or
2//! header, so a hit is almost always real.
3
4use regex::Regex;
5use std::sync::OnceLock;
6
7pub struct SecretPattern {
8    pub name: &'static str,
9    pub re: Regex,
10}
11
12const PATTERNS: &[(&str, &str)] = &[
13    ("private key", r"-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----"),
14    ("AWS access key id", r"\b(?:AKIA|ASIA)[0-9A-Z]{16}\b"),
15    ("GitHub token", r"\b(?:gh[pousr]_[A-Za-z0-9]{36,}|github_pat_[A-Za-z0-9_]{40,})\b"),
16    ("GitLab token", r"\bglpat-[A-Za-z0-9_-]{20,}\b"),
17    ("Slack token", r"\bxox[abposr]-[A-Za-z0-9-]{10,}\b"),
18    ("Google API key", r"\bAIza[0-9A-Za-z_-]{35}\b"),
19    ("Stripe live key", r"\b(?:sk|rk)_live_[0-9A-Za-z]{20,}\b"),
20    ("npm token", r"\bnpm_[A-Za-z0-9]{36}\b"),
21    ("Anthropic API key", r"\bsk-ant-[A-Za-z0-9_-]{20,}\b"),
22    ("OpenAI API key", r"\bsk-(?:proj|svcacct|admin)-[A-Za-z0-9_-]{20,}\b"),
23    ("Hugging Face token", r"\bhf_[A-Za-z]{34}\b"),
24];
25
26pub fn all() -> &'static [SecretPattern] {
27    static P: OnceLock<Vec<SecretPattern>> = OnceLock::new();
28    P.get_or_init(|| {
29        PATTERNS.iter().filter_map(|(name, re)| Regex::new(re).ok().map(|re| SecretPattern { name, re })).collect()
30    })
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    // Samples are assembled at run time so this file never holds a string that looks like a
38    // real credential to secret scanners.
39    #[test]
40    fn detects_known_formats_only() {
41        let hit = |s: &str| all().iter().find(|p| p.re.is_match(s)).map(|p| p.name);
42        assert_eq!(hit(&format!("-----BEGIN OPENSSH {}-----", "PRIVATE KEY")), Some("private key"));
43        assert_eq!(hit(&format!("key {}{} here", "AK", "IA".to_string() + &"X".repeat(16))), Some("AWS access key id"));
44        assert_eq!(hit(&format!("{}_{}", "ghp", "a".repeat(36))), Some("GitHub token"));
45        assert_eq!(hit("the word secret and sk_test_123"), None);
46    }
47}