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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
//! Random-token vs dictionary-identifier discriminator (KH-L-0413).
//!
//! The generic keyword bridge suppresses any value the identifier/type-name
//! shape gates flag (`pure_identifier_no_digit`, `pure_identifier`,
//! `type_name_shape`, `word_separated_identifier`). Those gates exist to drop
//! code references: `password = getUserName`, `secret = configValue`: but they
//! ALSO drop a large pool of REAL random passwords that happen to be all-letters
//! with no digit (`GRAPHITE_PASS=gjbubxsu`, `password="ufnlbbavawsdeecn"`,
//! `ftp://user:pxidztpv`): on CredData ~1114 keyword-anchored positives, measured
//! via the KH-L-0412 `--dogfood` trace.
//!
//! The two classes are SHAPE-identical (lowercase, no digit), so the only sound
//! discriminator is LANGUAGE STRUCTURE: a real random password has improbable
//! letter adjacencies (`gjb`, `xs`, `dz`), a dictionary identifier is built from
//! pronounceable English fragments (`get`, `user`, `config`). We score the mean
//! adjacent-bigram log-probability of the value's alphabetic runs against an
//! English bigram model (`data/english_bigram_logprob.bin`, generated by
//! `ml/gen_bigram_model.py` from a standard wordlist). A value whose letters are
//! collectively IMPROBABLE under English (mean log-prob below a threshold) is a
//! random token; a pronounceable one is a dictionary identifier.
//!
//! This is the SOUND half of the lever: lifting the identifier gates
//! unconditionally recovered +1023 CredData TP but added +3554 FP (precision
//! 0.60→0.40) by surfacing every `password = someVariable`; gating the lift on
//! `is_random_token` keeps the random passwords while leaving the identifier
//! references suppressed. Verified on BOTH bench corpora before landing.
use LazyLock;
/// English bigram log-probabilities, row-major `[a][b]` over `'a'..='z'`,
/// little-endian f32. Generated + committed by `ml/gen_bigram_model.py`; the
/// `.bin` is the reproducible source of truth (host-independent).
const BIGRAM_LOGPROB_BYTES: & = include_bytes!;
/// Parsed 26×26 model. Built once; the hot path indexes `[a*26 + b]`.
static BIGRAM_LOGPROB: = new;
/// Minimum alphabetic characters before a randomness verdict is meaningful.
/// Below this, English bigram statistics are too sparse to separate a short
/// random password from a short identifier, so we return `false` (NOT random ⇒
/// the identifier gate keeps suppressing (fail safe toward precision)).
pub const MIN_ALPHA: usize = 6;
/// Mean adjacent-bigram log-probability at or below which a token's letters are
/// collectively too improbable for English ⇒ a random token, not a dictionary
/// identifier. Calibrated on the CredData real-password vs identifier pools so
/// random passwords (≤ ~−7) pass while dictionary identifiers (≥ ~−6.4) do not;
/// the model's clean separation gap sits around −6.7.
const RANDOM_LOGPROB_THRESHOLD: f32 = -6.85;
/// Minimum DISTINCT lowercase letters a value must have for a `random` verdict.
/// A 1–2 distinct-letter token (`aaaaaaaa`, `xzxzxzxz`, `qqqqwwww`) has
/// improbable English bigrams, it would pass the log-prob threshold, but it is
/// a repetitive / alternating PATTERN, not a random token. Without this guard the
/// discriminator is only sound downstream of a caller-side entropy/diversity
/// floor (the generic bridge has one; the api.rs weak-anchor path does not). The
/// floor of 3 is data-calibrated: all 1285 CredData random passwords the
/// discriminator recovers have ≥ 4 distinct letters (min 4, e.g. `ttqqrqjt`),
/// while every blind-spot pattern has ≤ 2, so 3 separates them with margin and
/// drops no real password.
pub const MIN_DISTINCT_LETTERS: usize = 3;
pub
pub
/// `true` iff `value` reads as a RANDOM token (real credential) rather than a
/// pronounceable dictionary identifier (code reference) OR a low-diversity
/// repetitive pattern. Fails safe to `false` (treat as NOT random ⇒ keep
/// suppressing) when the value is too short/sparse to judge or has too few
/// distinct letters (soundness over reach, independent of any caller-side floor).
pub
/// `true` iff the bigram model is CONFIDENT that `value` is a pronounceable
/// English dictionary word, it has at least [`MIN_ALPHA`] alphabetic chars AND
/// its mean adjacent-bigram log-probability sits ABOVE the random threshold
/// (`password`, `secret`, `welcome`, `admin1234`).
///
/// This is the deliberate mirror image of [`is_random_token`], NOT merely its
/// negation: `!is_random_token` is also true for a SHORT token the model cannot
/// judge (`mean_bigram_logprob == None`, the fail-safe). This predicate stays
/// `false` there, so it can only ever DROP a value the model is sure is English
/// it never suppresses a short random password on a fail-safe, and a random
/// token (`pxidztpv`, score ≤ −6.85) is below the threshold so it is kept.
///
/// Used by the strong-anchor structural detectors (e.g. `url-credentials`,
/// whose regex proves a `scheme://user:<x>@host` credential SLOT but cannot
/// itself tell the literal placeholder word `password` from a real secret) to
/// drop the dictionary-word placeholders the Tier-B randomness floor would have
/// caught (without that floor's length penalty on short random passwords).
pub
/// `true` iff `value` has FEWER than [`MIN_DISTINCT_LETTERS`] distinct ASCII
/// letters, a repetitive / alternating / digit-only MASK (`xxxxxxxx`, `aaaaaa`,
/// `ababab`, `12345678`), never a real password.
///
/// This is the soundness companion to [`is_confident_dictionary_word`] for the
/// strong-anchor structural-password-slot family. Those detectors are
/// `is_service_anchored`, so the post-match pipeline sets `bypass_shape_gates`
/// and SKIPS the Tier-B repetitive-run / repeated-block gates that normally drop
/// a `--password xxxxxxxx` mask. `is_confident_dictionary_word` cannot catch a
/// mask (its bigrams are improbable English, so the model is NOT confident it is
/// a word), so without this guard the strong anchor would surface the mask as a
/// false positive. A genuinely-short random password (`i8cr1w!`, 4 distinct
/// letters) clears the floor and is kept, the same `MIN_DISTINCT_LETTERS = 3`
/// boundary [`is_random_token`] uses, so the two paths agree byte-for-byte.
pub
/// Shared decision for the CONTIGUOUS identifier/type-name shape gates
/// (KH-L-0413): keep the gate engaged (`true` ⇒ the value stays suppressed)
/// UNLESS the value reads as a random token, in which case lift it (`false` ⇒
/// recover the value). The single source of truth for the gate so the scan-time
/// generic bridge (`phase2_generic_shape`) and the post-process weak-anchor
/// path (`suppression::api::suppress_named_detector_finding`) agree
/// byte-for-byte (both wrap the SAME `is_random_token`, never a second copy).
///
/// Used ONLY for the contiguous gates (`pure_identifier` / `type_name`), whose
/// own predicates already reject digit-bearing values; the WORD-SEPARATED gate
/// needs the stricter [`keep_word_separated_gate_with_randomness`].
pub
/// Stricter sibling of [`keep_identifier_gate_with_randomness`] for the
/// WORD-SEPARATED identifier gate (KH-L-0414). The randomness model is an
/// ENGLISH-WORD model, and a multi-segment programmer identifier with embedded
/// digits / uppercase splits into SHORT acronym fragments (`d2i_PKCS7_bio` →
/// `pkcs`, `curlx_memdup0` → `memdup`) that the model mis-scores as random
/// so `is_random_token` alone is unsound here. Real CredData word-separated
/// passwords are uniformly all-lowercase letters + `_`/`-` separators
/// (`abxnj_gjvpuqzo`, `aapqhgn-qhuuc-trnmf`); requiring that shape BEFORE
/// trusting the randomness verdict recovers 141 real passwords while keeping
/// every acronym / product-key identifier (`d2i_PKCS7_bio`, `sqlite3_malloc64`,
/// `2iw9-n01w-Mc4V-faEC`) suppressed. Returns `true` (stay suppressed) for
/// anything that is not an all-lowercase-letter (+ separator) random token.
pub