1use std::sync::LazyLock;
2
3use regex::Regex;
4use smallvec::{SmallVec, smallvec};
5
6use crate::config::RuleType;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
9pub enum PatternType {
10 KeyValue,
12 Key,
14 Value,
16}
17
18#[derive(Clone, Debug, Eq, PartialEq)]
20pub enum ReplaceBehavior {
21 Value,
23
24 Groups(SmallVec<[u8; 1]>),
26}
27
28impl ReplaceBehavior {
29 pub fn replace_value() -> Self {
31 ReplaceBehavior::Value
32 }
33
34 pub fn replace_match() -> Self {
36 ReplaceBehavior::replace_group(0)
37 }
38
39 pub fn replace_group(g: u8) -> Self {
41 ReplaceBehavior::Groups(smallvec![g])
42 }
43
44 pub fn replace_groups(gs: SmallVec<[u8; 1]>) -> Self {
46 ReplaceBehavior::Groups(gs)
47 }
48}
49
50pub fn get_regex_for_rule_type(
52 ty: &RuleType,
53) -> SmallVec<[(PatternType, &Regex, ReplaceBehavior); 2]> {
54 let v = PatternType::Value;
55 let k = PatternType::Key;
56 let kv = PatternType::KeyValue;
57
58 match ty {
59 RuleType::RedactPair(redact_pair) => {
60 if let Ok(pattern) = redact_pair.key_pattern.compiled() {
61 smallvec![(kv, pattern, ReplaceBehavior::replace_value())]
62 } else {
63 smallvec![]
64 }
65 }
66 RuleType::Bearer => {
67 smallvec![(v, &*BEARER_TOKEN_REGEX, ReplaceBehavior::replace_match())]
68 }
69 RuleType::Password => {
70 smallvec![
71 (v, &*BEARER_TOKEN_REGEX, ReplaceBehavior::replace_match()),
74 (k, &*TOKEN_KEY_REGEX, ReplaceBehavior::replace_value()),
75 (kv, &*PASSWORD_KEY_REGEX, ReplaceBehavior::replace_value()),
76 ]
77 }
78 RuleType::Anything => smallvec![(v, &*ANYTHING_REGEX, ReplaceBehavior::replace_match())],
79 RuleType::Pattern(r) => {
80 let replace_behavior = match r.replace_groups {
81 Some(ref groups) => {
82 ReplaceBehavior::replace_groups(groups.iter().copied().collect())
83 }
84 None => ReplaceBehavior::replace_match(),
85 };
86 if let Ok(pattern) = r.pattern.compiled() {
87 smallvec![(v, pattern, replace_behavior)]
88 } else {
89 smallvec![]
90 }
91 }
92
93 RuleType::Imei => smallvec![(v, &*IMEI_REGEX, ReplaceBehavior::replace_match())],
94 RuleType::Mac => smallvec![(v, &*MAC_REGEX, ReplaceBehavior::replace_match())],
95 RuleType::Uuid => smallvec![(v, &*UUID_REGEX, ReplaceBehavior::replace_match())],
96 RuleType::Email => smallvec![(v, &*EMAIL_REGEX, ReplaceBehavior::replace_match())],
97 RuleType::Iban => smallvec![(v, &*IBAN_REGEX, ReplaceBehavior::replace_match())],
98 RuleType::Ip => smallvec![
99 (v, &*IPV4_REGEX, ReplaceBehavior::replace_match()),
100 (v, &*IPV6_REGEX, ReplaceBehavior::replace_group(1)),
101 ],
102 RuleType::Creditcard => {
103 smallvec![(v, &*CREDITCARD_REGEX, ReplaceBehavior::replace_match())]
104 }
105 RuleType::Pemkey => smallvec![(v, &*PEM_KEY_REGEX, ReplaceBehavior::replace_group(1))],
106 RuleType::UrlAuth => smallvec![(v, &*URL_AUTH_REGEX, ReplaceBehavior::replace_group(1))],
107 RuleType::UsSsn => smallvec![(v, &*US_SSN_REGEX, ReplaceBehavior::replace_match())],
108 RuleType::Userpath => smallvec![(v, &*PATH_REGEX, ReplaceBehavior::replace_group(1))],
109 RuleType::Cookies => smallvec![(kv, &*COOKIE_REGEX, ReplaceBehavior::replace_group(1))],
110
111 RuleType::Alias(_) | RuleType::Multiple(_) | RuleType::Unknown(_) => smallvec![],
113 }
114}
115
116#[rustfmt::skip]
117macro_rules! ip {
118 (v4s) => { "(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)" };
119 (v4a) => { concat!(ip!(v4s), "\\.", ip!(v4s), "\\.", ip!(v4s), "\\.", ip!(v4s)) };
120 (v6s) => { "[0-9a-fA-F]{1,4}" };
121}
122
123macro_rules! regex {
124 ($name:ident, $rule:expr) => {
125 #[allow(non_snake_case)]
126 mod $name {
127 use super::*;
128 pub static $name: LazyLock<Regex> = LazyLock::new(|| Regex::new($rule).unwrap());
129
130 #[test]
131 fn supports_byte_mode() {
132 assert!(
133 regex::bytes::RegexBuilder::new($name.as_str())
134 .unicode(false)
135 .multi_line(false)
136 .dot_matches_new_line(true)
137 .build()
138 .is_ok()
139 );
140 }
141 }
142 use $name::$name;
143 };
144}
145
146pub static ANYTHING_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(".*").unwrap());
147
148pub static SENSITIVE_COOKIE_NAMES: &[&str] = &[
149 "sentrysid", "sudo", "su", "session",
154 "__session",
155 "sessionid",
156 "user_session",
157 "symfony",
158 "phpsessid",
159 "fasthttpsessionid",
160 "mysession",
161 "irissessionid",
162 "_vercel_jwt",
163 "connect.sid",
164 "JSESSIONID",
165 "ASP.NET_SessionId",
166 "laravel_session",
167 "csrf",
169 "xsrf",
170 "_xsrf",
171 "_csrf",
172 "csrf-token",
173 "csrf_token",
174 "xsrf-token",
175 "xsrf_token",
176 "fastcsrf",
177 "_iris_csrf",
178];
179
180static COOKIE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
181 let variants = SENSITIVE_COOKIE_NAMES.join("|").replace('.', "\\.");
182 let r = format!("(?i)\\b(?:(?:{variants})(?:__\\d+)?)=([^;]+)");
183 Regex::new(&r).unwrap()
184});
185
186regex!(
187 IMEI_REGEX,
188 r"(?x)
189 \b
190 (\d{2}-?
191 \d{6}-?
192 \d{6}-?
193 \d{1,2})
194 \b
195 "
196);
197
198regex!(
199 MAC_REGEX,
200 r"(?x)
201 \b([[:xdigit:]]{2}[:-]){5}[[:xdigit:]]{2}\b
202 "
203);
204
205regex!(
206 UUID_REGEX,
207 r"(?ix)
208 \b
209 [a-z0-9]{8}-?
210 [a-z0-9]{4}-?
211 [a-z0-9]{4}-?
212 [a-z0-9]{4}-?
213 [a-z0-9]{12}
214 \b
215 "
216);
217
218regex!(
219 EMAIL_REGEX,
220 r"(?x)
221 \b
222 [a-zA-Z0-9.!\#$%&'*+/=?^_`{|}~-]+
223 @
224 [a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}
225 \b
226 "
227);
228
229regex!(
230 IBAN_REGEX,
231 r"(?x)
232 \b
233 (AT|AD|AE|AL|AZ|BA|BE|BG|BH|BR|BY|CH|CR|CY|CZ|DE|DK|DO|EE|EG|ES|FI|FO|FR|GB|GE|GI|GL|GR|GT|HR|HU|IE|IL|IQ|IS|IT|JO|KW|KZ|LB|LC|LI|LT|LU|LV|LY|MC|MD|ME|MK|MR|MT|MU|NL|NO|PK|PL|PS|PT|QA|RO|RU|RS|SA|SC|SE|SI|SK|SM|ST|SV|TL|TN|TR|UA|VA|VG|XK|DZ|AO|BJ|BF|BI|CV|CM|CF|TD|KM|CG|CI|DJ|GQ|GA|GW|HN|IR|MG|ML|MA|MZ|NI|NE|SN|TG)\d{2}[a-zA-Z0-9]{11,29}
234 \b
235 "
236);
237
238regex!(IPV4_REGEX, concat!("\\b", ip!(v4a), "\\b"));
239
240regex!(
241 IPV6_REGEX,
242 concat!(
243 "(?i)(?:[\\s]|[[:punct:]]|^)(",
244 "(",
245 ip!(v6s),
246 ":){7}",
247 ip!(v6s),
248 "|",
249 "(",
250 ip!(v6s),
251 ":){1,7}:|",
252 "(",
253 ip!(v6s),
254 ":){1,6}::",
255 ip!(v6s),
256 "|",
257 "(",
258 ip!(v6s),
259 ":){1,5}:(:",
260 ip!(v6s),
261 "){1,2}|",
262 "(",
263 ip!(v6s),
264 ":){1,4}:(:",
265 ip!(v6s),
266 "){1,3}|",
267 "(",
268 ip!(v6s),
269 ":){1,3}:(:",
270 ip!(v6s),
271 "){1,4}|",
272 "(",
273 ip!(v6s),
274 ":){1,2}:(:",
275 ip!(v6s),
276 "){1,5}|",
277 ip!(v6s),
278 ":((:",
279 ip!(v6s),
280 "){1,6})|",
281 ":((:",
282 ip!(v6s),
283 "){1,7}|:)|",
284 "fe80:(:",
285 ip!(v6s),
286 "){0,4}%[0-9a-zA-Z]{1,}",
287 "::(ffff(:0{1,4}){0,1}:){0,1}",
288 ip!(v4a),
289 "|",
290 "(",
291 ip!(v6s),
292 ":){1,4}:",
293 ip!(v4a),
294 ")([\\s]|[[:punct:]]|$)",
295 )
296);
297
298regex!(
304 CREDITCARD_REGEX,
305 r#"(?x)
306 \b(
307 (?: # vendor specific prefixes
308 3[47]\d # amex (no 13-digit version) (length: 15)
309 | 4\d{3} # visa (16-digit version only)
310 | 5[1-5]\d\d # mastercard
311 | 65\d\d # discover network (subset)
312 | 6011 # discover network (subset)
313 )
314
315 # "wildcard" remainder (allowing dashes in every position because of variable length)
316 ([-\s]?\d){12}
317 )\b
318 "#
319);
320
321regex!(
322 PATH_REGEX,
323 r"(?ix)
324 (?:
325 (?:
326 \b(?:[a-zA-Z]:[\\/])?
327 (?:users|home|documents and settings|[^/\\]+[/\\]profiles)[\\/]
328 ) | (?:
329 /(?:home|users)/
330 )
331 )
332 (
333 [^/\\\r\n\x00]+
334 )
335 "
336);
337
338regex!(
339 PEM_KEY_REGEX,
340 r"(?sx)
341 (?:
342 -----
343 BEGIN[A-Z\ ]+(?:PRIVATE|PUBLIC)\ KEY
344 -----
345 [\t\ ]*\r?\n?
346 )
347 (.+?)
348 (?:
349 \r?\n?
350 -----
351 END[A-Z\ ]+(?:PRIVATE|PUBLIC)\ KEY
352 -----
353 )
354 "
355);
356
357regex!(
358 URL_AUTH_REGEX,
359 r"(?x)
360 \b(?:
361 (?:[a-z0-9+-]+:)?//
362 ([a-zA-Z0-9%_.-]+(?::[a-zA-Z0-9%_.-]+)?)
363 )@
364 "
365);
366
367regex!(
368 US_SSN_REGEX,
369 r"(?x)
370 \b(
371 [0-9]{3}-
372 [0-9]{2}-
373 [0-9]{4}
374 )\b
375 "
376);
377
378regex!(BEARER_TOKEN_REGEX, r"(?i)\b(Bearer\s+)([^\s]+)");
379
380regex!(TOKEN_KEY_REGEX, r"(?i)(token)");
381
382regex!(
383 PASSWORD_KEY_REGEX,
384 r"(?i)(password|secret|passwd|api[-_]key|apikey|auth|credentials|mysql_pwd|privatekey|private[-_]key|token[^\s]*[:=]|^otp$|^two[-_]factor$)"
385);
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn test_userpath_utf8_bytes() {
393 let regex = regex::bytes::RegexBuilder::new(PATH_REGEX.as_str())
397 .unicode(false)
398 .multi_line(false)
399 .dot_matches_new_line(true)
400 .build()
401 .unwrap();
402 assert!(regex.is_match(br"C:\\Users\jane\somefile"));
403 }
404}