atuin_client/
secrets.rs

1// This file will probably trigger a lot of scanners. Sorry.
2
3use regex::RegexSet;
4use std::sync::LazyLock;
5
6pub enum TestValue<'a> {
7    Single(&'a str),
8    Multiple(&'a [&'a str]),
9}
10
11/// A list of `(name, regex, test)`, where `test` should match against `regex`.
12pub static SECRET_PATTERNS: &[(&str, &str, TestValue)] = &[
13    (
14        "AWS Access Key ID",
15        "AKIA[0-9A-Z]{16}",
16        TestValue::Single("AKIAIOSFODNN7EXAMPLE"),
17    ),
18    (
19        "AWS Secret Access Key env var",
20        "AWS_SECRET_ACCESS_KEY",
21        TestValue::Single("AWS_SECRET_ACCESS_KEY=KEYDATA"),
22    ),
23    (
24        "AWS Session Token env var",
25        "AWS_SESSION_TOKEN",
26        TestValue::Single("AWS_SESSION_TOKEN=KEYDATA"),
27    ),
28    (
29        "Microsoft Azure secret access key env var",
30        "AZURE_.*_KEY",
31        TestValue::Single("export AZURE_STORAGE_ACCOUNT_KEY=KEYDATA"),
32    ),
33    (
34        "Google cloud platform key env var",
35        "GOOGLE_SERVICE_ACCOUNT_KEY",
36        TestValue::Single("export GOOGLE_SERVICE_ACCOUNT_KEY=KEYDATA"),
37    ),
38    (
39        "Atuin login",
40        r"atuin\s+login",
41        TestValue::Single(
42            "atuin login -u mycoolusername -p mycoolpassword -k \"lots of random words\"",
43        ),
44    ),
45    (
46        "GitHub PAT (old)",
47        "ghp_[a-zA-Z0-9]{36}",
48        TestValue::Single("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"), // legit, I expired it
49    ),
50    (
51        "GitHub PAT (new)",
52        "gh1_[A-Za-z0-9]{21}_[A-Za-z0-9]{59}|github_pat_[0-9][A-Za-z0-9]{21}_[A-Za-z0-9]{59}",
53        TestValue::Multiple(&[
54            "gh1_1234567890abcdefghijk_1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklm",
55            "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", // also legit, also expired
56        ]),
57    ),
58    (
59        "GitHub OAuth Access Token",
60        "gho_[A-Za-z0-9]{36}",
61        TestValue::Single("gho_1234567890abcdefghijklmnopqrstuvwx000"), // not a real token
62    ),
63    (
64        "GitHub OAuth Access Token (user)",
65        "ghu_[A-Za-z0-9]{36}",
66        TestValue::Single("ghu_1234567890abcdefghijklmnopqrstuvwx000"), // not a real token
67    ),
68    (
69        "GitHub App Installation Access Token",
70        "ghs_[A-Za-z0-9]{36}",
71        TestValue::Single("ghs_1234567890abcdefghijklmnopqrstuvwx000"), // not a real token
72    ),
73    (
74        "GitHub Refresh Token",
75        "ghr_[A-Za-z0-9]{76}",
76        TestValue::Single(
77            "ghr_1234567890abcdefghijklmnopqrstuvwx1234567890abcdefghijklmnopqrstuvwx1234567890abcdefghijklmnopqrstuvwx",
78        ), // not a real token
79    ),
80    (
81        "GitHub App Installation Access Token v1",
82        "v1\\.[0-9A-Fa-f]{40}",
83        TestValue::Single("v1.1234567890abcdef1234567890abcdef12345678"), // not a real token
84    ),
85    (
86        "GitLab PAT",
87        "glpat-[a-zA-Z0-9_]{20}",
88        TestValue::Single("glpat-RkE_BG5p_bbjML21WSfy"),
89    ),
90    (
91        "Slack OAuth v2 bot",
92        "xoxb-[0-9]{11}-[0-9]{11}-[0-9a-zA-Z]{24}",
93        TestValue::Single("xoxb-17653672481-19874698323-pdFZKVeTuE8sk7oOcBrzbqgy"),
94    ),
95    (
96        "Slack OAuth v2 user token",
97        "xoxp-[0-9]{11}-[0-9]{11}-[0-9a-zA-Z]{24}",
98        TestValue::Single("xoxp-17653672481-19874698323-pdFZKVeTuE8sk7oOcBrzbqgy"),
99    ),
100    (
101        "Slack webhook",
102        "T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}",
103        TestValue::Single(
104            "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
105        ),
106    ),
107    (
108        "Stripe test key",
109        "sk_test_[0-9a-zA-Z]{24}",
110        TestValue::Single("sk_test_1234567890abcdefghijklmnop"),
111    ),
112    (
113        "Stripe live key",
114        "sk_live_[0-9a-zA-Z]{24}",
115        TestValue::Single("sk_live_1234567890abcdefghijklmnop"),
116    ),
117    (
118        "Netlify authentication token",
119        "nf[pcoub]_[0-9a-zA-Z]{36}",
120        TestValue::Single("nfp_nBh7BdJxUwyaBBwFzpyD29MMFT6pZ9wq5634"),
121    ),
122    (
123        "npm token",
124        "npm_[A-Za-z0-9]{36}",
125        TestValue::Single("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"),
126    ),
127    (
128        "Pulumi personal access token",
129        "pul-[0-9a-f]{40}",
130        TestValue::Single("pul-683c2770662c51d960d72ec27613be7653c5cb26"),
131    ),
132];
133
134/// The `regex` expressions from [`SECRET_PATTERNS`] compiled into a `RegexSet`.
135pub static SECRET_PATTERNS_RE: LazyLock<RegexSet> = LazyLock::new(|| {
136    let exprs = SECRET_PATTERNS.iter().map(|f| f.1);
137    RegexSet::new(exprs).expect("Failed to build secrets regex")
138});
139
140#[cfg(test)]
141mod tests {
142    use regex::Regex;
143
144    use crate::secrets::{SECRET_PATTERNS, TestValue};
145
146    #[test]
147    fn test_secrets() {
148        for (name, regex, test) in SECRET_PATTERNS {
149            let re =
150                Regex::new(regex).unwrap_or_else(|_| panic!("Failed to compile regex for {name}"));
151
152            match test {
153                TestValue::Single(test) => {
154                    assert!(re.is_match(test), "{name} test failed!");
155                }
156                TestValue::Multiple(tests) => {
157                    for test_str in tests.iter() {
158                        assert!(
159                            re.is_match(test_str),
160                            "{name} test with value \"{test_str}\" failed!"
161                        );
162                    }
163                }
164            }
165        }
166    }
167}