1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// SPDX-License-Identifier: Apache-2.0
//! What an admission rule can match across a line end — the one derivation
//! shared by everything that admits a text in pieces cut at line ends: the
//! gateway's chunked scan ([`super::detectors`]) and the unit reader of a
//! transcript above the read cap (`tools::ctx_read`, `core::import::bounded`).
//!
//! BUILT-IN rules. Every one is line-local (its match, or at least the
//! existence of its match, depends on one line only) except:
//!
//! | rule | crosses a line end by | reach |
//! |---|---|---|
//! | phone candidate, `input_filters::pii` — validation reads 24 characters before and after the match | its context | bounded: under [`BOUNDED_REACH_BYTES`] |
//! | classification field, `input_filters::classification` — `^\s*key\s*[:=]\s*(.+)$` | white space between key, separator and value | NOT bounded |
//! | private-key block mask, `core::redaction` — `(?s)BEGIN … .+? … END` | anything | NOT bounded |
//!
//! A piece boundary is safe for a bounded rule when the text within its reach
//! on both sides is judged together (a seam). For a rule without a bound
//! only a boundary outside any possible match is safe
//! ([`ends_inside_classification_field`]; the private-key block is applied to
//! whole texts by the gateway and is checked where units are admitted).
//!
//! CONFIGURED rules (policy-pack patterns, `secret_detection.custom_patterns`)
//! are free regular expressions with no bound on what they match. One is
//! applied to pieces only when it is PROVEN unable to consume a line feed
//! ([`pattern_is_single_line`]); any other is applied to the whole text, or
//! the text is not admitted in pieces at all.
use OnceLock;
use Regex;
/// The longest reach of a built-in rule across a line end that HAS a bound,
/// in bytes on one side of the line end.
///
/// - Phone candidate: the match (no line end inside; under 100 bytes even
/// with non-ASCII digits) plus 24 characters of context (at most 96 bytes)
/// on the side that crosses the line end.
///
/// 256 covers it with room to spare.
pub const BOUNDED_REACH_BYTES: usize = 256;
/// Whether `text` ends INSIDE a possible classification field: after the key
/// (`classification`, `sensitivity`, `data class`) at the start of a line, or
/// after the key and its `:` / `=`, with nothing but white space — any
/// amount, line ends included — behind it. The value the rule would read is
/// then in the text that follows.
pub
/// Whether `pattern` (regex-crate syntax) is PROVEN unable to consume a line
/// feed, so every match lies inside one line. Conservative: `false` for
/// anything not proven — such a pattern counts as multi-line and unbounded.
///
/// Refused, because each can match `\n` or hide something that can: a literal
/// control character; any escape other than `\d \w \S \b \B \A \z`, an
/// escaped punctuation character, or `\t` not used as the start of a range;
/// a negated class `[^…]`; a POSIX class `[:…:]`; a flag group that sets `s`.
/// What remains — literals, `.` without `s`, positive classes of literals
/// and the escapes above, anchors, groups, repetition — cannot match `\n`:
/// a range reaches it only from a start below it, and those are control
/// characters.
pub