use crate::error::{RuntimeError, RuntimeResult};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SecretMatch {
pub detector: &'static str,
pub masked: String,
}
impl std::fmt::Display for SecretMatch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"content matches secret pattern {} at masked excerpt {}. {}",
self.detector,
self.masked,
block_guidance(self.detector)
)
}
}
fn block_guidance(detector: &'static str) -> &'static str {
match detector {
"high-entropy-token"
| "uuid-near-trigger"
| "content-hash-near-trigger"
| "hex-credential-token" => {
"If this is a real credential, remove it before writing. If it is not \
(e.g. a file path, UUID, or hash that happens to sit near a word like \
key/secret/auth/token), reword so the token is not glued directly next \
to that word — e.g. move it to its own line, or insert a space or word \
between them."
}
_ => {
"If this is a real credential, remove it before writing; store secrets \
in an env var or secrets manager instead."
}
}
}
pub fn check(content: &str) -> RuntimeResult<()> {
if let Some(m) = scan(content) {
return Err(RuntimeError::SecretDetected(m));
}
Ok(())
}
pub fn check_json(value: &serde_json::Value) -> RuntimeResult<()> {
scan_json_value(value)
}
pub fn check_tags(tags: &[String]) -> RuntimeResult<()> {
for tag in tags {
check(tag)?;
}
Ok(())
}
fn scan_json_value(value: &serde_json::Value) -> RuntimeResult<()> {
match value {
serde_json::Value::String(s) => check(s),
serde_json::Value::Array(arr) => {
for v in arr {
scan_json_value(v)?;
}
Ok(())
}
serde_json::Value::Object(map) => {
for (k, v) in map {
check(k)?;
scan_json_value(v)?;
}
Ok(())
}
_ => Ok(()),
}
}
const REDACTION_MARKER: &str = "***MASKED***";
fn scan_match(text: &str) -> Option<(&str, &'static str)> {
scan_from(text, 0)
}
fn scan_from(text: &str, from: usize) -> Option<(&str, &'static str)> {
let base = text.as_ptr() as usize;
let mut best = check_known_patterns(&text[from..]);
keep_leftmost(&mut best, check_entropy_heuristic(text, from), base);
best
}
fn keep_leftmost<'a>(
best: &mut Option<(&'a str, &'static str)>,
cand: Option<(&'a str, &'static str)>,
base: usize,
) {
if let Some((slice, name)) = cand {
let start = slice.as_ptr() as usize - base;
let replace = match *best {
Some((incumbent, _)) => start < (incumbent.as_ptr() as usize - base),
None => true,
};
if replace {
*best = Some((slice, name));
}
}
}
fn scan(text: &str) -> Option<SecretMatch> {
scan_match(text).map(|(slice, detector)| build_match(detector, slice))
}
pub fn mask_secrets(text: &str) -> std::borrow::Cow<'_, str> {
let base = text.as_ptr() as usize;
let mut spans: Vec<(usize, usize)> = Vec::new();
let mut from = 0;
while from < text.len() {
match scan_from(text, from) {
Some((sub, _detector)) => {
let start = sub.as_ptr() as usize - base;
let core_len = sub
.trim_end_matches(['"', '\'', '`', '}', ']', ')', ',', ';'])
.len();
let end = start + core_len.max(1);
spans.push((start, end));
from = end;
}
None => break,
}
}
if spans.is_empty() {
return std::borrow::Cow::Borrowed(text);
}
let mut out = String::with_capacity(text.len());
let mut cursor = 0;
for (start, end) in spans {
let start = start.max(cursor);
out.push_str(&text[cursor..start]);
out.push_str(REDACTION_MARKER);
cursor = end.max(cursor);
}
out.push_str(&text[cursor..]);
std::borrow::Cow::Owned(out)
}
const PREFIX_DETECTORS: &[(&str, &str, usize)] = &[
("aws-access-key-id", "AKIA", 20),
("aws-access-key-id", "ASIA", 20),
("github-token", "ghp_", 36),
("github-token", "gho_", 36),
("github-token", "ghu_", 36),
("github-token", "ghs_", 36),
("github-token", "ghr_", 36),
("github-token", "github_pat_", 20),
("openai-api-key", "sk-proj-", 40),
("anthropic-api-key", "sk-ant-", 20),
("stripe-secret-key", "sk_live_", 30),
("stripe-restricted-key", "rk_live_", 30),
("fly-token", "fm2_", 20),
("vercel-token", "vercel_", 20),
("slack-token", "xoxb-", 40),
("slack-token", "xoxa-", 40),
("slack-token", "xoxp-", 40),
("slack-token", "xoxr-", 40),
("slack-token", "xoxs-", 40),
("age-secret-key", "AGE-SECRET-KEY-", 60),
];
const SK_SAFE_PREFIXES: &[&str] = &["sk-learn", "sk-image", "sk-lego", "sk-base", "sk-misc"];
fn check_known_patterns(text: &str) -> Option<(&str, &'static str)> {
let base = text.as_ptr() as usize;
let mut best: Option<(&str, &'static str)> = None;
for &(name, needle, min_len) in PREFIX_DETECTORS {
keep_leftmost(
&mut best,
find_prefix_token(text, needle, min_len).map(|m| (m, name)),
base,
);
}
if let Some(token) = find_prefix_token(text, "sk-", 30) {
if !SK_SAFE_PREFIXES.iter().any(|safe| token.starts_with(safe)) {
keep_leftmost(&mut best, Some((token, "openai-api-key")), base);
}
}
if let Some(pos) = text.find("FlyV1 ") {
let at_boundary = pos == 0 || {
text[..pos]
.chars()
.next_back()
.is_none_or(|c| !c.is_ascii_alphanumeric())
};
if at_boundary {
let payload_start = pos + 6; let payload = extract_token(&text[payload_start..]);
if payload.len() >= 4 {
let candidate = &text[pos..payload_start + payload.len()];
keep_leftmost(&mut best, Some((candidate, "fly-token")), base);
}
}
}
if text.contains("-----BEGIN") && text.contains("PRIVATE KEY-----") {
if let Some(pos) = text.find("-----BEGIN") {
let block_end = text[pos..]
.find("-----END")
.map(|rel| {
text[pos + rel..]
.find('\n')
.map(|l| pos + rel + l + 1)
.unwrap_or(text.len())
})
.unwrap_or(text.len());
let excerpt = &text[pos..block_end];
keep_leftmost(&mut best, Some((excerpt, "pem-private-key")), base);
}
}
keep_leftmost(&mut best, find_jwt(text).map(|m| (m, "jwt")), base);
keep_leftmost(
&mut best,
find_url_userinfo(text).map(|m| (m, "url-userinfo")),
base,
);
best
}
fn find_prefix_token<'a>(text: &'a str, needle: &str, min_len: usize) -> Option<&'a str> {
let mut start = 0;
while let Some(rel) = text[start..].find(needle) {
let abs = start + rel;
let at_boundary = abs == 0 || {
let prev = text[..abs].chars().next_back().unwrap_or(' ');
!prev.is_ascii_alphanumeric()
};
if at_boundary {
let token = extract_token(&text[abs..]);
if token.len() >= min_len {
return Some(token);
}
}
start = abs + needle.len().max(1);
}
None
}
fn find_jwt(text: &str) -> Option<&str> {
let bytes = text.as_bytes();
let mut i = 0;
while i + 4 < bytes.len() {
if bytes[i..].starts_with(b"eyJ") {
let end = bytes[i..]
.iter()
.position(|&b| b == b' ' || b == b'\n' || b == b'\r' || b == b'\t')
.map(|p| i + p)
.unwrap_or(bytes.len());
let candidate = &text[i..end];
let dots = candidate.as_bytes().iter().filter(|&&b| b == b'.').count();
if dots >= 2 {
let parts: Vec<&str> = candidate.splitn(3, '.').collect();
if parts.len() == 3
&& parts[0].starts_with("eyJ")
&& parts[1].starts_with("eyJ")
&& parts[0].len() >= 10
&& parts[1].len() >= 10
{
return Some(candidate);
}
}
i = end + 1;
} else {
i += 1;
}
}
None
}
fn find_url_userinfo(text: &str) -> Option<&str> {
let mut search = text;
let mut base = 0usize;
while let Some(at_rel) = search.find("://") {
let at_abs = base + at_rel;
let rest_start = at_abs + 3;
let rest = &text[rest_start..];
if let Some(at_pos) = rest.find('@') {
let userinfo = &rest[..at_pos];
if let Some(colon) = userinfo.find(':') {
let user = &userinfo[..colon];
let pass = &userinfo[colon + 1..];
if !user.is_empty() && !pass.is_empty() && pass.len() >= 4 {
let scheme_start = text[..at_abs]
.char_indices()
.rev()
.find(|(_, c)| {
!c.is_ascii_alphanumeric() && *c != '+' && *c != '-' && *c != '.'
})
.map(|(idx, c)| idx + c.len_utf8())
.unwrap_or(0);
if !userinfo.contains(' ') && !userinfo.contains('\n') {
let end = rest_start
+ at_pos
+ 1
+ rest[at_pos + 1..]
.find([' ', '\n', '\r'])
.unwrap_or(rest[at_pos + 1..].len());
return Some(&text[scheme_start..end.min(text.len())]);
}
}
}
}
base = at_abs + 3;
search = &text[base..];
}
None
}
const TRIGGER_WORDS: &[&str] = &[
"key",
"secret",
"password",
"passwd",
"credential",
"bearer",
"auth",
"apikey",
];
const COMPOUND_TRIGGER_WORDS: &[&str] = &["api_key", "access_key", "private_key"];
const MIN_ENTROPY_LEN: usize = 24;
const ENTROPY_THRESHOLD: f64 = 4.5;
const TRIGGER_WINDOW: usize = 120;
fn floor_char_boundary(s: &str, i: usize) -> usize {
let mut i = i.min(s.len());
while i > 0 && !s.is_char_boundary(i) {
i -= 1;
}
i
}
fn check_entropy_heuristic(text: &str, from: usize) -> Option<(&str, &'static str)> {
let tokens: Vec<(usize, &str)> = text
.split(|c: char| c.is_ascii_whitespace() || !c.is_ascii())
.filter(|t| !t.is_empty())
.map(|t| {
let offset = t.as_ptr() as usize - text.as_ptr() as usize;
(offset, t)
})
.collect();
for &(tok_offset, raw_token) in &tokens {
let token = strip_delimiters(raw_token);
let token_offset = token.as_ptr() as usize - text.as_ptr() as usize;
if token_offset < from {
continue;
}
if token.len() < MIN_ENTROPY_LEN {
continue;
}
let window_start = floor_char_boundary(text, tok_offset.saturating_sub(TRIGGER_WINDOW));
let window_end = floor_char_boundary(text, tok_offset + raw_token.len() + TRIGGER_WINDOW);
let window = &text[window_start..window_end];
let raw_start = tok_offset - window_start;
let raw_end = raw_start + raw_token.len();
let near_trigger = contains_trigger(&window[..raw_start])
|| contains_trigger(&window[raw_end..])
|| has_inline_credential_trigger(raw_token);
if near_trigger && value_candidates(token).any(is_uuid_canonical) {
return Some((token, "uuid-near-trigger"));
}
if near_trigger && value_candidates(token).any(is_base64_content_hash) {
return Some((token, "content-hash-near-trigger"));
}
if !near_trigger && (is_uuid_canonical(token) || is_base64_content_hash(token)) {
continue;
}
if !near_trigger && is_pure_hex(token) {
continue;
}
const HEX_CREDENTIAL_LENGTHS: &[usize] = &[32, 40, 64, 128];
if near_trigger && is_pure_hex(token) && HEX_CREDENTIAL_LENGTHS.contains(&token.len()) {
return Some((token, "hex-credential-token"));
}
if !near_trigger && is_structured_identifier(token) {
continue;
}
let entropy = shannon_entropy(token.as_bytes());
if entropy < ENTROPY_THRESHOLD {
continue;
}
if near_trigger {
return Some((token, "high-entropy-token"));
}
}
None
}
fn contains_word(low_window: &str, needle: &str, underscore_is_word_char: bool) -> bool {
let is_word_char = |c: char| c.is_ascii_alphanumeric() || (underscore_is_word_char && c == '_');
let mut start = 0;
while let Some(rel) = low_window[start..].find(needle) {
let abs = start + rel;
let before_ok = abs == 0
|| low_window[..abs]
.chars()
.next_back()
.is_none_or(|c| !is_word_char(c));
let after_end = abs + needle.len();
let after_ok = after_end >= low_window.len()
|| low_window[after_end..]
.chars()
.next()
.is_none_or(|c| !is_word_char(c));
if before_ok && after_ok {
return true;
}
start = abs + needle.len().max(1);
}
false
}
fn contains_bounded_word(low_window: &str, needle: &str) -> bool {
contains_word(low_window, needle, false)
}
fn contains_compound_trigger(low_text: &str) -> bool {
COMPOUND_TRIGGER_WORDS.iter().any(|needle| {
let mut start = 0;
while let Some(rel) = low_text[start..].find(needle) {
let abs = start + rel;
let before_ok = abs == 0
|| low_text[..abs]
.chars()
.next_back()
.is_none_or(|c| !c.is_ascii_alphanumeric());
if before_ok {
return true;
}
start = abs + needle.len();
}
false
})
}
fn contains_trigger(text: &str) -> bool {
let low = text.to_ascii_lowercase();
TRIGGER_WORDS
.iter()
.any(|tw| contains_bounded_word(&low, tw))
|| contains_compound_trigger(&low)
|| has_standalone_token(&low)
|| has_token_assignment(&low)
|| has_assignment_credential_trigger(&low)
}
fn has_assignment_credential_trigger(low_text: &str) -> bool {
low_text.char_indices().any(|(index, ch)| {
if !matches!(ch, '=' | ':') {
return false;
}
let before =
low_text[..index].trim_end_matches(|c: char| !c.is_ascii_alphanumeric() && c != '_');
let label = before
.rsplit(|c: char| !c.is_ascii_alphanumeric() && c != '_')
.next()
.unwrap_or_default();
COMPOUND_TRIGGER_WORDS
.iter()
.any(|needle| label.contains(needle))
|| TRIGGER_WORDS
.iter()
.any(|tw| contains_bounded_word(label, tw))
|| label == "token"
})
}
fn has_inline_credential_trigger(raw_token: &str) -> bool {
let low = raw_token.to_ascii_lowercase();
if has_assignment_credential_trigger(&low) {
return true;
}
!low.contains(['/', '-', '.'])
&& low.contains('_')
&& (COMPOUND_TRIGGER_WORDS
.iter()
.any(|needle| low.contains(needle))
|| TRIGGER_WORDS
.iter()
.any(|tw| contains_bounded_word(&low, tw)))
}
fn has_standalone_token(low_window: &str) -> bool {
contains_word(low_window, "token", true)
}
fn has_token_assignment(low_window: &str) -> bool {
let needle = "token";
let mut start = 0;
while let Some(rel) = low_window[start..].find(needle) {
let abs = start + rel;
let before_ok = abs == 0
|| low_window[..abs]
.chars()
.next_back()
.is_none_or(|c| !c.is_ascii_alphanumeric() && c != '_');
let after_end = abs + needle.len();
let after_char = low_window[after_end..].chars().next();
let after_is_assign = matches!(after_char, Some('=') | Some(':'));
if before_ok && after_is_assign {
return true;
}
start = abs + needle.len().max(1);
}
false
}
fn is_pure_hex(token: &str) -> bool {
let hex_part = token
.strip_prefix("0x")
.or(token.strip_prefix("0X"))
.unwrap_or(token);
hex_part.len() >= 8 && hex_part.len() <= 128 && hex_part.bytes().all(|b| b.is_ascii_hexdigit())
}
fn is_base64_content_hash(token: &str) -> bool {
const VENDOR_PREFIXES: &[&str] = &[
"sk-",
"rk_live_",
"fm2_",
"vercel_",
"xoxb-",
"xoxa-",
"xoxp-",
"xoxr-",
"xoxs-",
"ghp_",
"gho_",
"ghu_",
"ghs_",
"ghr_",
"github_pat_",
"AKIA",
"ASIA",
"AGE-SECRET-KEY-",
"FlyV1",
];
if VENDOR_PREFIXES.iter().any(|p| token.starts_with(p)) {
return false;
}
let body = if let Some(rest) = token.strip_prefix("sha") {
let dash = rest.find('-').unwrap_or(rest.len());
let digits = &rest[..dash];
if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()) && dash < rest.len() {
&rest[dash + 1..] } else {
return false; }
} else {
return false; };
let stripped = body.trim_end_matches('=');
let pad_removed = body.len() - stripped.len();
if pad_removed > 2 {
return false;
}
let n = stripped.len();
if n != 43 && n != 64 && !(86..=88).contains(&n) {
return false;
}
stripped
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'+' || b == b'/' || b == b'-' || b == b'_')
}
const STRUCTURAL_SEPARATORS: [char; 4] = ['/', '-', '_', '.'];
const MAX_RUN_LEN: usize = 24;
const DENSITY_EXEMPT_LETTER_LEN: usize = 4;
const MAX_CASE_TRANSITION_DENSITY: f64 = 0.3;
fn is_structured_identifier(token: &str) -> bool {
if !token.contains(|c: char| STRUCTURAL_SEPARATORS.contains(&c)) {
return false;
}
let runs: Vec<&str> = token
.split(|c: char| !c.is_ascii_alphanumeric())
.filter(|r| !r.is_empty())
.collect();
runs.len() >= 2 && runs.iter().all(|run| is_word_shaped_run(run))
}
fn is_word_shaped_run(run: &str) -> bool {
if run.is_empty() || run.len() > MAX_RUN_LEN {
return false;
}
let bytes = run.as_bytes();
if bytes.iter().all(|b| b.is_ascii_digit()) {
return true;
}
let letter_end = bytes
.iter()
.position(|b| !b.is_ascii_alphabetic())
.unwrap_or(bytes.len());
if letter_end == 0 {
return false;
}
if !bytes[letter_end..].iter().all(|b| b.is_ascii_digit()) {
return false;
}
case_transition_density_ok(&run[..letter_end])
}
fn case_transition_density_ok(letters: &str) -> bool {
let chars: Vec<char> = letters.chars().collect();
if chars.len() <= DENSITY_EXEMPT_LETTER_LEN {
return true;
}
let transitions = chars
.windows(2)
.filter(|w| w[0].is_ascii_uppercase() != w[1].is_ascii_uppercase())
.count();
let density = transitions as f64 / (chars.len() - 1) as f64;
density <= MAX_CASE_TRANSITION_DENSITY
}
fn is_uuid_canonical(s: &str) -> bool {
let b = s.as_bytes();
if b.len() != 36 {
return false;
}
b[8] == b'-'
&& b[13] == b'-'
&& b[18] == b'-'
&& b[23] == b'-'
&& b[..8].iter().all(|c| c.is_ascii_hexdigit())
&& b[9..13].iter().all(|c| c.is_ascii_hexdigit())
&& b[14..18].iter().all(|c| c.is_ascii_hexdigit())
&& b[19..23].iter().all(|c| c.is_ascii_hexdigit())
&& b[24..].iter().all(|c| c.is_ascii_hexdigit())
}
fn strip_delimiters(s: &str) -> &str {
s.trim_matches(|c| matches!(c, '"' | '\'' | '`' | ':' | '=' | ',' | ';'))
}
fn strip_wrappers(s: &str) -> &str {
s.trim_matches(|c: char| {
matches!(
c,
'{' | '}' | '(' | ')' | '[' | ']' | '"' | '\'' | '`' | '.' | ',' | ';'
)
})
}
fn wrapper_strip_repeated(token: &str) -> &str {
let mut cur = token;
loop {
let next = strip_wrappers(cur);
if next == cur {
return cur;
}
cur = next;
}
}
fn value_candidates(token: &str) -> impl Iterator<Item = &str> {
let cur = wrapper_strip_repeated(token);
std::iter::once(cur).chain(cur.char_indices().filter_map(move |(i, c)| {
if c == '=' || c == ':' {
let after = strip_wrappers(&cur[i + c.len_utf8()..]);
if !after.is_empty() {
return Some(after);
}
}
None
}))
}
fn extract_token(s: &str) -> &str {
let end = s
.find(|c: char| c.is_whitespace() || c == '\n' || c == '\r')
.unwrap_or(s.len());
&s[..end]
}
fn shannon_entropy(bytes: &[u8]) -> f64 {
if bytes.is_empty() {
return 0.0;
}
let mut counts = [0u32; 256];
for &b in bytes {
counts[b as usize] += 1;
}
let len = bytes.len() as f64;
counts
.iter()
.filter(|&&c| c > 0)
.map(|&c| {
let p = c as f64 / len;
-p * p.log2()
})
.sum()
}
fn build_match(detector: &'static str, candidate: &str) -> SecretMatch {
let chars: Vec<char> = candidate.chars().collect();
let preview: String = chars.iter().take(6).collect();
let masked = format!("{}...{}chars", preview, chars.len());
SecretMatch { detector, masked }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn blocks_aws_akia() {
let fake = "AKIAFAKEKEY1234567890";
assert!(scan(fake).is_some(), "AKIA must be caught");
let m = scan(fake).unwrap();
assert_eq!(m.detector, "aws-access-key-id");
assert!(
!m.masked.contains("FAKEKEY1234567890"),
"must not echo the secret: {}",
m.masked
);
}
#[test]
fn blocks_aws_asia() {
let fake = "ASIAFAKEKEY00000000000";
let m = scan(fake);
assert!(m.is_some(), "ASIA must be caught");
assert_eq!(m.unwrap().detector, "aws-access-key-id");
}
#[test]
fn blocks_github_ghp() {
let fake = "ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
assert!(scan(fake).is_some(), "ghp_ must be caught");
}
#[test]
fn blocks_github_gho() {
let fake = "gho_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
assert!(scan(fake).is_some(), "gho_ must be caught");
}
#[test]
fn blocks_github_pat() {
let fake = "github_pat_AAAAAABBBBBBCCCCCC";
assert!(scan(fake).is_some(), "github_pat_ must be caught");
}
#[test]
fn blocks_openai_sk() {
let fake = "sk-aaaaaabbbbbbccccccddddddeeeeeeffffgg";
assert!(scan(fake).is_some(), "sk- must be caught");
}
#[test]
fn blocks_anthropic_sk_ant() {
let fake = "sk-ant-api03-AAAAAAAAAAAAAAA";
assert!(scan(fake).is_some(), "sk-ant- must be caught");
assert_eq!(scan(fake).unwrap().detector, "anthropic-api-key");
}
#[test]
fn blocks_stripe_live() {
let fake = "sk_live_FAKESTRIPE0000000000000"; assert!(scan(fake).is_some(), "sk_live_ must be caught");
assert_eq!(scan(fake).unwrap().detector, "stripe-secret-key");
}
#[test]
fn blocks_stripe_restricted() {
let fake = "rk_live_FAKESTRIPE0000000000000"; assert!(scan(fake).is_some(), "rk_live_ must be caught");
assert_eq!(scan(fake).unwrap().detector, "stripe-restricted-key");
}
#[test]
fn blocks_fly_flyv1() {
let fake = "FlyV1 FAKEFLYTOKEN000000000000000000";
assert!(scan(fake).is_some(), "FlyV1 must be caught");
assert_eq!(scan(fake).unwrap().detector, "fly-token");
}
#[test]
fn blocks_fly_fm2() {
let fake = "fm2_FAKEFLYTOKEN00000000000000000";
assert!(scan(fake).is_some(), "fm2_ must be caught");
assert_eq!(scan(fake).unwrap().detector, "fly-token");
}
#[test]
fn blocks_vercel_token() {
let fake = "vercel_FAKETOKEN00000000000000000";
assert!(scan(fake).is_some(), "vercel_ must be caught");
assert_eq!(scan(fake).unwrap().detector, "vercel-token");
}
#[test]
fn blocks_slack_xoxb() {
let fake = "xoxb-FAKE-SLACKTOKEN-000000000000000000000000";
assert!(scan(fake).is_some(), "xoxb- must be caught");
assert_eq!(scan(fake).unwrap().detector, "slack-token");
}
#[test]
fn blocks_pem_private_key() {
let header = ["-----BEGIN RSA", " PRIVATE KEY-----"].concat(); let fake = format!("{}\nMIIEo\u{2026}\n-----END RSA PRIVATE KEY-----", header);
assert!(scan(&fake).is_some(), "PEM private key must be caught");
assert_eq!(scan(&fake).unwrap().detector, "pem-private-key");
}
#[test]
fn blocks_pem_ec_private_key() {
let header = ["-----BEGIN EC", " PRIVATE KEY-----"].concat(); let fake = format!("{}\nMHQCAQEE\u{2026}\n-----END EC PRIVATE KEY-----", header);
assert!(scan(&fake).is_some(), "EC PEM must be caught");
}
#[test]
fn blocks_age_secret_key() {
let fake = "AGE-SECRET-KEY-1QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ";
assert!(scan(fake).is_some(), "AGE-SECRET-KEY- must be caught");
assert_eq!(scan(fake).unwrap().detector, "age-secret-key");
}
#[test]
fn blocks_jwt_triple() {
let fake = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.FAKE_SIG_XXXXXXXXXXXX"; assert!(scan(fake).is_some(), "JWT triple must be caught");
assert_eq!(scan(fake).unwrap().detector, "jwt");
}
#[test]
fn blocks_url_userinfo() {
let fake = "postgresql://dbuser:S3cr3tP4ss@db.example.com:5432/mydb";
assert!(scan(fake).is_some(), "URL userinfo must be caught");
assert_eq!(scan(fake).unwrap().detector, "url-userinfo");
}
#[test]
fn blocks_high_entropy_near_bearer_word() {
let fake = "Bearer token: Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM"; assert!(
scan(fake).is_some(),
"high-entropy value near 'bearer' must be caught"
);
assert_eq!(scan(fake).unwrap().detector, "high-entropy-token");
}
#[test]
fn blocks_high_entropy_near_secret_word() {
let fake = "secret=Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM"; assert!(
scan(fake).is_some(),
"high-entropy value near 'secret' must be caught"
);
}
#[test]
fn error_message_masks_secret() {
let fake = "ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let m = scan(fake).unwrap();
let masked = &m.masked;
assert!(
!masked.contains("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
"mask must not echo the full secret value; got: {masked}"
);
assert!(
masked.starts_with("ghp_AA"),
"mask must show first 6 chars; got: {masked}"
);
}
#[test]
fn allows_sha256_hex() {
let sha = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
assert!(
scan(sha).is_none(),
"sha256 hex must pass (allowlisted); fired: {:?}",
scan(sha)
);
}
#[test]
fn allows_uuid() {
let uuid = "550e8400-e29b-41d4-a716-446655440000";
assert!(
scan(uuid).is_none(),
"UUID must pass; fired: {:?}",
scan(uuid)
);
}
#[test]
fn allows_git_sha() {
let sha = "d362950a3c9b1a4cb47d97f1623e38f1a1e6bcdf";
assert!(
scan(sha).is_none(),
"git SHA must pass; fired: {:?}",
scan(sha)
);
}
#[test]
fn allows_normal_prose() {
let prose =
"The FlashAttention paper introduces IO-aware tiling for transformer self-attention.";
assert!(scan(prose).is_none(), "normal prose must pass");
}
#[test]
fn allows_code_snippet() {
let code = r#"fn create_entity(name: &str, kind: &str) -> RuntimeResult<Entity> {
self.validate_entity_kind(kind)?;
Ok(Entity::new("local", kind, name))
}"#;
assert!(
scan(code).is_none(),
"code snippet must pass; fired: {:?}",
scan(code)
);
}
#[test]
fn allows_long_url_without_credentials() {
let url = "https://docs.example.com/api/v2/entities?kind=concept&limit=100";
assert!(scan(url).is_none(), "URL without userinfo must pass");
}
#[test]
fn allows_base64_image_stub() {
let b64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIABQ";
assert!(
scan(b64).is_none(),
"base64 image stub without trigger word must pass; fired: {:?}",
scan(b64)
);
}
#[test]
fn allows_long_plain_url() {
let url = "https://api.github.com/repos/ohdearquant/khive/pulls/76/comments?per_page=100";
assert!(
scan(url).is_none(),
"plain URL must pass; fired: {:?}",
scan(url)
);
}
#[test]
fn allows_manifest_content_hash() {
let line =
"checksum = \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"";
assert!(
scan(line).is_none(),
"manifest content hash line must pass; fired: {:?}",
scan(line)
);
}
#[test]
fn masked_excerpt_format() {
let fake = "AKIAFAKEKEY1234567890";
let m = scan(fake).unwrap();
assert!(m.masked.contains("..."), "masked must contain '...'");
assert!(m.masked.ends_with("chars"), "masked must end with 'chars'");
}
#[test]
fn check_returns_ok_for_safe_content() {
assert!(check("A normal memory note about LoRA.").is_ok());
}
#[test]
fn check_returns_err_for_secret() {
let fake = "AKIAFAKEKEY1234567890";
let result = check(fake);
assert!(result.is_err(), "check must fail for AKIA key");
let err = result.unwrap_err();
assert!(
matches!(err, RuntimeError::SecretDetected(_)),
"error variant must be SecretDetected"
);
}
#[test]
fn entropy_of_uniform_string_is_zero() {
let s = "aaaaaaaaaaaaaaaa";
assert!(shannon_entropy(s.as_bytes()) < 0.01);
}
#[test]
fn entropy_of_random_bytes_is_high() {
let s = b"X9kZ2vQpLrT8nJwYuAeHfBsDcGiONvM1"; assert!(shannon_entropy(s) > 4.5, "entropy={}", shannon_entropy(s));
}
#[test]
fn cjk_prose_near_trigger_is_not_flagged() {
let content = "更新 auth 配置数据库连接管理系统核心模块设计文档";
assert!(
check(content).is_ok(),
"CJK prose near a trigger word must not be flagged as a secret"
);
}
#[test]
fn ascii_secret_near_trigger_still_flagged() {
let content = "api_key X9kZ2vQpLrT8nJwYuAeHfBsDcGiONvM1";
assert!(
check(content).is_err(),
"ASCII high-entropy token near a trigger word must still be blocked"
);
}
#[test]
fn ascii_secret_in_cjk_context_does_not_panic_and_is_flagged() {
let cjk = "数据库连接管理系统核心模块设计文档".repeat(6); let content = format!("{cjk}x api_key X9kZ2vQpLrT8nJwYuAeHfBsDcGiONvM1 {cjk}");
assert!(
check(&content).is_err(),
"ASCII secret in CJK context must still be blocked (and must not panic)"
);
}
#[test]
fn ascii_secret_glued_to_cjk_is_still_flagged() {
let secret = "X9kZ2vQpLrT8nJwYuAeHfBsDcGiONvM1"; let cases = [
format!("api_key {secret}数据"), format!("api_key 「{secret}」"), format!("api_key {secret}"), format!("api_key:{secret}"), format!("数据{secret}更新 api_key"), ];
for content in &cases {
assert!(
check(content).is_err(),
"ASCII secret glued to CJK must be blocked: {content:?}"
);
}
}
#[test]
fn high_entropy_ascii_run_without_trigger_is_not_flagged() {
let secret = "X9kZ2vQpLrT8nJwYuAeHfBsDcGiONvM1"; let content = format!("数据库连接{secret}核心模块设计文档");
assert!(
check(&content).is_ok(),
"high-entropy ASCII run with no trigger word must not be flagged"
);
}
#[test]
fn known_prefix_secret_glued_after_cjk_is_still_flagged() {
let cases = [
"数据AKIAIOSFODNN7EXAMPLE", "令牌github_pat_11ABCDEFG0HIJKLMNOPQR", "密钥sk-ant-api03-AAAAAAAAAAAAAAAAAA", "配置FlyV1 fm2_AAAABBBBCCCCDDDD", ];
for content in cases {
assert!(
check(content).is_err(),
"known-prefix secret glued after CJK must be blocked: {content:?}"
);
}
}
#[test]
fn url_userinfo_after_cjk_does_not_panic_and_is_flagged() {
let cases = [
"数据postgresql://dbuser:S3cr3tP4ss@db.example.com/db", "配置mysql://root:hunter2pw@10.0.0.1:3306/app", "连接redis://svc:V3ryS3cretPw@cache.internal:6379", ];
for content in cases {
assert!(
check(content).is_err(),
"credential URL after CJK must be blocked, not panic: {content:?}"
);
}
}
#[test]
fn non_ascii_glued_token_trigger_is_still_flagged() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let blocked = [
format!("数据token={opaque}"), format!("配置token: {opaque}"), format!("密钥token {opaque}"), format!("résumétoken: {opaque}"), format!("1token: {opaque}"), ];
for content in &blocked {
assert!(
check(content).is_err(),
"non-ASCII-glued token trigger must flag the value: {content:?}"
);
}
let allowed = [
format!("数据next_token: {opaque}"),
format!("数据token_count: {opaque}"),
format!("servicetoken: {opaque}"),
];
for content in &allowed {
assert!(
check(content).is_ok(),
"compound token identifier must not be flagged: {content:?}"
);
}
}
#[test]
fn allowlist_passes_sha256() {
let sha = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
assert!(is_pure_hex(sha));
}
#[test]
fn allowlist_passes_uuid_canonical() {
assert!(is_uuid_canonical("550e8400-e29b-41d4-a716-446655440000"));
}
#[test]
fn allowlist_does_not_pass_mixed_token() {
assert!(!is_pure_hex("sk-aaaaaabbbbbbccccccddddddeeeeeeffffgg"));
}
#[test]
fn check_json_blocks_secret_in_object_value() {
let props = serde_json::json!({ "api_key": "AKIAFAKEKEY1234567890" });
assert!(
check_json(&props).is_err(),
"secret in properties object value must be blocked"
);
}
#[test]
fn check_json_blocks_secret_in_nested_object() {
let props = serde_json::json!({ "credentials": { "token": "sk-proj-FAKEKEY00000000000000000000000000000000" } }); assert!(
check_json(&props).is_err(),
"secret in nested properties object must be blocked"
);
}
#[test]
fn check_json_blocks_secret_in_array() {
let props = serde_json::json!(["normal", "AKIAFAKEKEY1234567890"]);
assert!(
check_json(&props).is_err(),
"secret in JSON array must be blocked"
);
}
#[test]
fn check_json_passes_safe_properties() {
let props = serde_json::json!({
"domain": "attention",
"status": "researched",
"year": 2024
});
assert!(
check_json(&props).is_ok(),
"normal properties must pass; fired: {:?}",
check_json(&props).err()
);
}
#[test]
fn check_tags_blocks_credential_tag() {
let tags = vec![
"type:concept".to_string(),
"AKIAFAKEKEY1234567890".to_string(),
];
assert!(
check_tags(&tags).is_err(),
"credential-shaped tag must be blocked"
);
}
#[test]
fn check_tags_passes_normal_tags() {
let tags = vec!["type:concept".to_string(), "domain:attention".to_string()];
assert!(
check_tags(&tags).is_ok(),
"normal tags must pass; fired: {:?}",
check_tags(&tags).err()
);
}
#[test]
fn allows_sk_learn_prose() {
let texts = &[
"sk-learn is a Python machine learning library",
"sk-learn-compatible transformer pipeline reference",
"sk-learn scikit-learn estimator interface",
];
for t in texts {
assert!(
scan(t).is_none(),
"sk-learn prose must pass; fired: {:?} on {:?}",
scan(t),
t
);
}
}
#[test]
fn blocks_openai_sk_proj_not_confused_with_sk_learn() {
let fake = "sk-proj-FAKEKEY00000000000000000000000000000000"; assert!(
scan(fake).is_some(),
"sk-proj- key must still be caught after sk-learn exemption"
);
}
#[test]
fn blocks_sri_hash_near_key_word_accepted_fp() {
let line = "integrity key: sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC";
assert!(
scan(line).is_some(),
"SRI hash near trigger word 'key' must now be blocked (accepted FP); passed unexpectedly"
);
}
#[test]
fn allows_base64_tokenizer_hash_metadata() {
let line = "tokenizer_vocab_hash: Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM"; assert!(
scan(line).is_none(),
"tokenizer hash metadata must pass; fired: {:?}",
scan(line)
);
}
#[test]
fn allows_npm_lockfile_integrity() {
let body_86 = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM1234567890abcdefghijklmnopqrstuvwxABCDEFGHIJKLMNOPQRST";
assert_eq!(body_86.len(), 86, "test body must be exactly 86 chars");
let line = format!(
"resolved: https://registry.npmjs.org/foo/-/foo-1.0.0.tgz\nintegrity: sha512-{body_86}=="
);
assert!(
scan(&line).is_none(),
"npm lockfile integrity must pass; fired: {:?}",
scan(&line)
);
}
#[test]
fn allows_tokenizer_vocab_hash_no_block() {
let line = "tokenizer_vocab_hash = Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM"; assert!(
scan(line).is_none(),
"tokenizer_vocab_hash must pass; 'token' is only standalone-word matched; fired: {:?}",
scan(line)
);
}
#[test]
fn blocks_bare_base64url_43chars_near_key() {
let token_43 = "wJalrXUtnFEMI-K7MDENGbPxRfiCYEXAMPLEKEYX123"; assert_eq!(token_43.len(), 43, "test token must be exactly 43 chars");
let line = format!("api key {token_43}");
assert!(
scan(&line).is_some(),
"43-char base64url token near 'key' must be caught (no sha-prefix = not a hash); fired: {:?}",
scan(&line)
);
}
#[test]
fn blocks_bare_base64url_64chars_near_secret() {
let token_64 = "wJalrXUtnFEMI-K7MDENGbPxRfiCYEXAMPLEKEYX123wJalrXUtnFEMI-K7MDENa"; assert_eq!(token_64.len(), 64, "test token must be exactly 64 chars");
let line = format!("secret: {token_64}");
assert!(
scan(&line).is_some(),
"64-char base64url token near 'secret' must be caught; got: {:?}",
scan(&line)
);
}
#[test]
fn blocks_bare_base64url_86chars_near_auth() {
let token_86 = "wJalrXUtnFEMI-K7MDENGbPxRfiCYEXAMPLEKEYX123wJalrXUtnFEMI-K7MDENwJalrXUtnFEMI-K7MDENabc"; assert_eq!(token_86.len(), 86, "test token must be exactly 86 chars");
let line = format!("auth header {token_86}");
assert!(
scan(&line).is_some(),
"86-char base64url token near 'auth' must be caught; got: {:?}",
scan(&line)
);
}
#[test]
fn blocks_service_token_opaque_value() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; assert!(
opaque.len() >= 24,
"opaque must be long enough for entropy check"
);
let line = format!("service token {opaque}");
assert!(
scan(&line).is_some(),
"service token <opaque> must be caught by standalone 'token' check; got: {:?}",
scan(&line)
);
}
#[test]
fn blocks_token_equals_credential() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let line = format!("token={opaque}");
assert!(
scan(&line).is_some(),
"token=<value> must be caught via token= trigger; got: {:?}",
scan(&line)
);
}
#[test]
fn blocks_token_colon_credential() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let line = format!("token: {opaque}");
assert!(
scan(&line).is_some(),
"token: <value> must be caught via token: trigger; got: {:?}",
scan(&line)
);
}
#[test]
fn allows_next_token_technical_context() {
let line = "next_token: cursor-page-2-abcdef12345678";
assert!(
scan(line).is_none(),
"next_token technical context must not be blocked; fired: {:?}",
scan(line)
);
}
#[test]
fn allows_next_token_high_entropy_cursor() {
let cursor = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let line = format!("next_token: {cursor}");
assert!(
scan(&line).is_none(),
"next_token with high-entropy cursor must pass (compound identifier); fired: {:?}",
scan(&line)
);
}
#[test]
fn allows_token_count_high_entropy() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let line = format!("token_count: {opaque}");
assert!(
scan(&line).is_none(),
"token_count with high-entropy value must pass; fired: {:?}",
scan(&line)
);
}
#[test]
fn hex_near_key_blocked_in_credential_context() {
let hex32 = "4f9c2e8a1d3b5c7e9f0a2b4d6e8c0a2b";
assert_eq!(hex32.len(), 32);
let line = format!("api key {hex32}");
assert!(
scan(&line).is_some(),
"32-char pure hex near 'api key' must be blocked; got None"
);
}
#[test]
fn hex_credential_lengths_blocked_near_trigger() {
let hex40 = "a3f5c2e9d1b8047e63a1f4c2d5b6e8f1a9c3d2e4";
let hex64 = "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b";
let hex128 = format!("{hex64}{hex64}");
assert_eq!(hex40.len(), 40);
assert_eq!(hex64.len(), 64);
assert_eq!(hex128.len(), 128);
for (label, hex) in &[
("hex40", hex40),
("hex64", hex64),
("hex128", hex128.as_str()),
] {
let line = format!("secret key: {hex}");
assert!(
scan(&line).is_some(),
"{label} near 'secret key' must be blocked; got None"
);
}
}
#[test]
fn hex_blocked_when_trigger_and_hash_word_coexist() {
let hex32 = "4f9c2e8a1d3b5c7e9f0a2b4d6e8c0a2b";
let key_hash_line = format!("api key hash {hex32}");
let secret_sha_line = format!("secret sha {hex32}");
assert!(
scan(&key_hash_line).is_some(),
"'api key hash <hex32>' must be blocked; got None"
);
assert!(
scan(&secret_sha_line).is_some(),
"'secret sha <hex32>' must be blocked; got None"
);
}
#[test]
fn hex_near_sha_context_word_allowed() {
let hex40 = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
let sha_line = format!("sha1: {hex40}");
let commit_line = format!("commit sha {hex40}");
assert!(
scan(&sha_line).is_none(),
"hex40 near 'sha1' context must be allowed; fired: {:?}",
scan(&sha_line)
);
assert!(
scan(&commit_line).is_none(),
"hex40 near 'commit sha' context must be allowed; fired: {:?}",
scan(&commit_line)
);
}
#[test]
fn hex64_near_hash_context_allowed() {
let hex64 = "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b";
let sha_line = format!("sha256: {hex64}");
let hash_line = format!("hash value {hex64}");
assert!(
scan(&sha_line).is_none(),
"hex64 near 'sha256' must be allowed; fired: {:?}",
scan(&sha_line)
);
assert!(
scan(&hash_line).is_none(),
"hex64 near 'hash' must be allowed; fired: {:?}",
scan(&hash_line)
);
}
#[test]
fn blocks_high_entropy_hex_like_token_near_key() {
let mixed = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM"; assert!(!is_pure_hex(mixed), "test token must not be pure hex");
let line = format!("api key {mixed}");
assert!(
scan(&line).is_some(),
"mixed-charset high-entropy token near 'api key' must be caught; got: {:?}",
scan(&line)
);
}
#[test]
fn allows_hex40_without_trigger() {
let hex40 = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
let line = format!("commit: {hex40}");
assert!(
scan(&line).is_none(),
"40-char hex without trigger word must pass; fired: {:?}",
scan(&line)
);
}
#[test]
fn check_json_blocks_secret_in_object_key() {
let props = serde_json::json!({ "ghp_FakeGitHubToken0000000000000000000": "redacted" }); assert!(
check_json(&props).is_err(),
"credential as JSON object key must be blocked"
);
}
#[test]
fn check_json_blocks_nested_secret_key() {
let props = serde_json::json!({
"metadata": {
"AKIAFAKEKEY000000000": "value" }
});
assert!(
check_json(&props).is_err(),
"nested credential as JSON object key must be blocked"
);
}
#[test]
fn pem_masked_excerpt_reflects_block_length_not_rest_of_string() {
let header = ["-----BEGIN RSA", " PRIVATE KEY-----"].concat(); let fake = format!(
"{}\nMIIEo\u{2026}\n-----END RSA PRIVATE KEY-----\nsome trailing text that is very long",
header
);
let m = scan(&fake).unwrap();
assert_eq!(m.detector, "pem-private-key");
let full_len = fake.chars().count();
let reported_len: usize = m
.masked
.trim_end_matches("chars")
.rsplit("...")
.next()
.and_then(|s| s.parse().ok())
.unwrap_or(full_len + 1);
assert!(
reported_len < full_len,
"masked length ({reported_len}) should be less than full string length ({full_len})"
);
}
#[test]
fn utf8_build_match_preview_multibyte_prefix_no_panic() {
let header = ["-----BEGIN RSA", " PRIVATE KEY-----"].concat(); let fake = format!("{}\n🔑密钥\n-----END RSA PRIVATE KEY-----", header);
let m = scan(&fake);
assert!(m.is_some(), "PEM with emoji body must still be caught");
let m = m.unwrap();
assert!(
!m.masked.contains("🔑密钥"),
"mask must not echo the emoji body"
);
}
#[test]
fn utf8_extract_token_multibyte_suffix_no_panic() {
let text = "FlyV1 ABCDEFGHIJ密钥";
let _ = scan(text);
}
#[test]
fn utf8_prefix_detector_multibyte_adjacent_no_panic() {
let text = "🔑AKIAFAKEKEY00000000000000";
let _ = scan(text);
let text2 = "éghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let _ = scan(text2);
let text3 = "AKIAFAKEKEY00000000000000🔑";
let _ = scan(text3); }
#[test]
fn utf8_jwt_multibyte_adjacent_no_panic() {
let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.FAKE_SIG_XXXXXXXXXXXX"; let text = format!("数据{jwt}密钥");
let _ = scan(&text);
let text2 = format!("{jwt}\u{3000}morecontent");
let _ = scan(&text2);
let text3 = format!("{jwt}🔑");
let _ = scan(&text3); }
#[test]
fn utf8_url_userinfo_multibyte_scheme_no_panic() {
let cases = [
"🔑postgresql://dbuser:S3cr3tP4ss@db.example.com/db", "密钥mysql://root:hunter2pw@10.0.0.1:3306/app", "éredis://svc:V3ryS3cretPw@cache.internal:6379", ];
for text in &cases {
let result = scan(text);
assert!(
result.is_some(),
"URL credential after multibyte must be caught: {text:?}"
);
}
}
#[test]
fn utf8_entropy_window_multibyte_boundary_no_panic() {
let cjk_fill = "数".repeat(39); assert_eq!(cjk_fill.len(), 117);
let secret = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM1"; let content = format!("{cjk_fill}xy key {secret}");
let _ = scan(&content);
let content2 = format!("key {secret}{cjk_fill}xy");
let _ = scan(&content2); }
#[test]
fn utf8_no_panic_property_test() {
let multibyte_items = [
"🔑", "密", "é", "\u{3000}", "🇺🇸", ];
let secrets = [
"AKIAFAKEKEY00000000000000",
"ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"sk-ant-api03-AAAAAAAAAAAAAAA",
"Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM1",
"FlyV1 fm2_AAAABBBBCCCCDDDDEEEEFFFF",
];
for mb in &multibyte_items {
for secret in &secrets {
for sep in &["", " ", "\n"] {
let s = format!("{mb}{sep}{secret}");
let _ = check(&s);
let s = format!("{secret}{sep}{mb}");
let _ = check(&s);
let s = format!("{mb}{sep}{secret}{sep}{mb}");
let _ = check(&s);
let fill = mb.repeat(50);
let s = format!("{fill} api_key {secret} {fill}");
let _ = check(&s);
}
}
}
}
#[test]
fn mask_secrets_borrows_clean_text() {
let clean = "The FlashAttention paper introduces IO-aware tiling.";
let masked = mask_secrets(clean);
assert!(
matches!(masked, std::borrow::Cow::Borrowed(_)),
"clean text must not allocate"
);
assert_eq!(masked, clean);
}
#[test]
fn mask_secrets_redacts_shapes_the_old_mirror_regex_missed() {
let cases = [
"key: sk-proj-FAKEKEY00000000000000000000000000000000", "cred ASIAFAKEKEY00000000000", "stripe sk_live_FAKESTRIPE0000000000000", "db postgresql://dbuser:S3cr3tP4ss@db.example.com/db", ];
for c in &cases {
let masked = mask_secrets(c);
assert!(
masked.contains(REDACTION_MARKER),
"must redact: {c:?} -> {masked:?}"
);
}
}
#[test]
fn mask_secrets_redacts_every_span_and_keeps_prose() {
let line =
"first sk-ant-api03-AAAAAAAAAAAAAAA then ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA end";
let masked = mask_secrets(line);
assert!(
!masked.contains("sk-ant-api03") && !masked.contains("ghp_AAAA"),
"no secret may survive: {masked}"
);
assert_eq!(
masked.matches(REDACTION_MARKER).count(),
2,
"both secrets must be redacted: {masked}"
);
assert!(masked.starts_with("first "), "prose preserved: {masked}");
assert!(masked.ends_with(" end"), "prose preserved: {masked}");
}
#[test]
fn mask_secrets_output_passes_check() {
let line = "token=ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA and AKIAFAKEKEY1234567890";
let masked = mask_secrets(line).into_owned();
assert!(
check(&masked).is_ok(),
"masked output must pass the gate: {masked}"
);
}
#[test]
fn mask_secrets_redacts_entropy_secret_left_of_known_secret() {
let line =
"secret=Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM and ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; let masked = mask_secrets(line).into_owned();
assert!(
!masked.contains("Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM") && !masked.contains("ghp_AAAA"),
"neither the entropy secret nor the known secret may survive: {masked}"
);
assert_eq!(
masked.matches(REDACTION_MARKER).count(),
2,
"both secrets must be redacted exactly once: {masked}"
);
assert!(
check(&masked).is_ok(),
"masked output must pass the gate: {masked}"
);
}
#[test]
fn github_app_token_families_are_masked() {
let cases = [
"ghu_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "ghs_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB", "ghr_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC", ];
for token in &cases {
assert!(
check(token).is_err(),
"gate must hard-block GitHub App token {token}"
);
let line = format!("auth: {token} trailing");
let masked = mask_secrets(&line).into_owned();
assert!(
!masked.contains(token),
"GitHub App token must not survive masking: {masked}"
);
assert!(
check(&masked).is_ok(),
"masked output must pass the gate: {masked}"
);
}
}
#[test]
fn mask_secrets_redacts_entropy_token_whose_trigger_is_left_of_earlier_secret() {
let line =
"api_key ghp_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM1"; let masked = mask_secrets(line).into_owned();
assert!(
!masked.contains("ghp_AAAA"),
"the known secret must be redacted: {masked}"
);
assert!(
!masked.contains("Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM1"),
"the later entropy token must be redacted even though its trigger \
word sits left of the earlier redaction: {masked}"
);
assert_eq!(
masked.matches(REDACTION_MARKER).count(),
2,
"both secrets must be redacted exactly once: {masked}"
);
assert!(
check(&masked).is_ok(),
"masked output must pass the gate: {masked}"
);
}
#[test]
fn blocks_high_entropy_file_path_near_secret_word() {
let content =
"workspace path fable-ops/ADR-DRAFT-adr079-slices234.md for the secret gate bug";
assert!(
check(content).is_err(),
"accepted FP: structured file path near 'secret' is now \
blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_high_entropy_workspace_path_near_key_word() {
let content = "key: see internal/workspaces/20260701/adr079-slices234/PACKET.md";
assert!(
check(content).is_err(),
"accepted FP: workspace path near 'key' is now blocked; \
got {:?}",
scan(content)
);
}
#[test]
fn blocks_high_entropy_short_run_path_near_auth_word() {
let content =
"auth work saved at internal/workspaces/20260701/cloud-rebuild/R1-repo-audit.md";
assert!(
check(content).is_err(),
"accepted FP: path with a short 'R1' run near 'auth' is \
now blocked; got {:?}",
scan(content)
);
}
#[test]
fn allows_branch_and_review_filename_near_key_word() {
let content =
"branch feat-session-mirror pushed, see review_pr335_round2.md for the key findings";
assert!(
check(content).is_ok(),
"branch name and review filename near 'key' must not be blocked; fired: {:?}",
scan(content)
);
}
#[test]
fn allows_adr_doc_path_near_password_word() {
let content = "password reset doc: docs/adr/ADR-055-epistemic-edge-relations.md";
assert!(
check(content).is_ok(),
"ADR doc path near 'password' must not be blocked; fired: {:?}",
scan(content)
);
}
#[test]
fn allows_source_file_path_near_credential_word() {
let content = "credential handling code crates/khive-pack-session/src/mirror/ingest.rs";
assert!(
check(content).is_ok(),
"source file path near 'credential' must not be blocked; fired: {:?}",
scan(content)
);
}
#[test]
fn allows_long_snake_case_identifier_near_key_word() {
let content = "api key handling lives in check_entropy_heuristic_impl";
assert!(
check(content).is_ok(),
"snake_case identifier near 'key' must not be blocked; fired: {:?}",
scan(content)
);
}
#[test]
fn hyphenated_random_secret_is_not_a_structured_identifier() {
assert!(!is_structured_identifier(
"wJalrXUtnFEMI-K7MDENGbPxRfiCYEXAMPLEKEYX123"
));
let line = "api key wJalrXUtnFEMI-K7MDENGbPxRfiCYEXAMPLEKEYX123";
assert!(
scan(line).is_some(),
"hyphenated random secret must still be blocked; got: {:?}",
scan(line)
);
}
#[test]
fn structured_identifier_true_for_repro_paths() {
let paths = [
"fable-ops/ADR-DRAFT-adr079-slices234.md",
"internal/workspaces/20260701/adr079-slices234/PACKET.md",
"internal/workspaces/20260701/cloud-rebuild/R1-repo-audit.md",
"review_pr335_round2.md",
"docs/adr/ADR-055-epistemic-edge-relations.md",
"crates/khive-pack-session/src/mirror/ingest.rs",
"check_entropy_heuristic_impl",
];
for p in paths {
assert!(
is_structured_identifier(p),
"expected structured identifier: {p}"
);
}
}
#[test]
fn structured_identifier_false_without_separator() {
assert!(!is_structured_identifier(
"Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvM"
));
}
#[test]
fn structured_identifier_false_for_leetspeak_digit_interleaving() {
assert!(!is_structured_identifier("S3cr3t-P4ssw0rd-t0ken-here!"));
}
#[test]
fn structured_identifier_false_for_run_over_length_cap() {
let long_run = "a".repeat(26);
let token = format!("prefix-{long_run}-suffix");
assert!(!is_structured_identifier(&token));
}
#[test]
fn blocks_separator_secret_access_key_bypass() {
let content = "secret_access_key abcdefghij/klmnopqrst/uvwxyzabcd/efghijk";
assert!(
check(content).is_err(),
"AWS Secret Access Key shaped bypass must be blocked: {:?}",
scan(content)
);
}
#[test]
fn blocks_adversarial_lowercase_only_separator_token_near_access_key() {
let content = "access_key qrstuvwxyz/abcdefghij/klmnopqrst/uvwxyzab";
assert!(
check(content).is_err(),
"lowercase-only separator-delimited high-entropy token near \
'access_key' must be blocked: {:?}",
scan(content)
);
}
#[test]
fn blocks_adversarial_digit_and_word_mixed_token_near_api_key() {
let content = "api_key attaycofrsm827/festwqjhc493/8261947350/qwikjzx982";
assert!(
check(content).is_err(),
"digit-and-word-mixed high-entropy token near 'api_key' must be blocked: {:?}",
scan(content)
);
}
#[test]
fn blocks_adversarial_token_assignment_separator_delimited_secret() {
let content = "token=zxkqwmvbpl/trfhysjgnc/dweiaoutkz-mnbvcxzlk";
assert!(
check(content).is_err(),
"token= with lowercase-only separator-delimited high-entropy value \
must be blocked: {:?}",
scan(content)
);
}
#[test]
fn blocks_extension_suffix_bypass_secret_access_key() {
let content = "secret_access_key abcdefghij/klmnopqrst/uvwxyzabcd/efghijk.md";
assert!(
check(content).is_err(),
"extension-suffixed AWS Secret Access Key shaped bypass must be blocked: {:?}",
scan(content)
);
}
#[test]
fn blocks_extension_suffix_bypass_token_assignment() {
let content = "token=zxkqwmvbpl/trfhysjgnc/dweiaoutkz-mnbvcxzlk.rs";
assert!(
check(content).is_err(),
"extension-suffixed token= bypass must be blocked: {:?}",
scan(content)
);
}
#[test]
fn blocks_unsuffixed_separator_split_credential_bypasses() {
let cases = [
"secret_access_key abcdefghij/klmnopqrst/uvwxyzabcd/efghijk",
"token=zxkqwmvbpl/trfhysjgnc/dweiaoutkz-mnbvcxzlk",
];
for content in cases {
assert!(
check(content).is_err(),
"bypass string must still be blocked: {content:?}, got: {:?}",
scan(content)
);
}
}
#[test]
fn blocks_digit_run_suffix_bypass_attempt() {
let cases = [
"secret_access_key abcdefghij/klmnopqrst/uvwxyzabcd/efghijk2024",
"secret_access_key abcdefghij2024/klmnopqrst/uvwxyzabcd/efghijk.md",
];
for content in cases {
assert!(
check(content).is_err(),
"digit-run-suffixed bypass attempt must be blocked: {content:?}, got: {:?}",
scan(content)
);
}
}
#[test]
fn blocks_low_entropy_padding_run_bypass_attempts() {
let cases = [
"secret_access_key abcdefghij/klmnopqrst/uvwxyzabcd/efghijk/aaaa/R1.md",
"token=zxkqwmvbpl/trfhysjgnc/dweiaoutkz/mnbvcxzlk/aaaa/R1.rs",
"secret_access_key abcdefghij/klmnopqrst/uvwxyzabcd/efghijk/aaaa/bbbb/R1.md",
"token=zxkqwmvbpl/trfhysjgnc/dweiaoutkz/mnbvcxzlk/aaaa/bbbb/R1.rs",
];
for content in cases {
assert!(
check(content).is_err(),
"padding-run bypass attempt must be blocked: {content:?}, got: {:?}",
scan(content)
);
}
}
#[test]
fn blocks_run_splitting_bypass_attempts() {
let cases = [
"secret_access_key abcd/efgh/ijkl/mnop/qrst/uvwx/yzab/cdef.md",
"secret_access_key abcde/fghij/klmno/pqrst/uvwxy/zabcd.md",
"secret_access_key abcdef/ghijkl/mnopqr/stuvwx/yzabcd.md",
];
for content in cases {
assert!(
check(content).is_err(),
"run-splitting bypass attempt must be blocked: {content:?}, got: {:?}",
scan(content)
);
}
}
#[test]
fn allows_fp_paths_whose_full_token_entropy_is_already_below_threshold() {
let paths = [
"review_pr335_round2.md",
"docs/adr/ADR-055-epistemic-edge-relations.md",
"crates/khive-pack-session/src/mirror/ingest.rs",
"check_entropy_heuristic_impl",
];
for p in paths {
let content = format!("api_key handling in {p}");
assert!(
check(&content).is_ok(),
"{p} must stay allowed near 'api_key' (full-token entropy already \
below threshold): fired {:?}",
scan(&content)
);
}
}
#[test]
fn accepted_false_positive_adr_draft_path_near_trigger() {
let content = "api_key handling in fable-ops/ADR-DRAFT-adr079-slices234.md";
assert!(
check(content).is_err(),
"accepted FP: ADR-DRAFT path near 'api_key' is now blocked; \
got {:?}",
scan(content)
);
}
#[test]
fn accepted_false_positive_workspace_packet_path_near_trigger() {
let content = "api_key handling in internal/workspaces/20260701/adr079-slices234/PACKET.md";
assert!(
check(content).is_err(),
"accepted FP: PACKET.md workspace path near 'api_key' is now blocked \
got {:?}",
scan(content)
);
}
#[test]
fn blocks_high_entropy_repo_audit_path_near_api_key() {
let content =
"api_key handling in internal/workspaces/20260701/cloud-rebuild/R1-repo-audit.md";
assert!(
check(content).is_err(),
"accepted FP: R1-repo-audit path near 'api_key' is now blocked; \
got {:?}",
scan(content)
);
}
#[test]
fn blocks_uuid_directly_labeled_as_api_key() {
let content = "api_key 550e8400-e29b-41d4-a716-446655440000";
assert!(
check(content).is_err(),
"UUID-shaped token labeled api_key must be blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_sha256_content_hash_labeled_as_secret() {
let content = "secret sha256-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq";
assert!(
check(content).is_err(),
"sha256-prefixed hash labeled secret must be blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_sha384_content_hash_labeled_as_api_key() {
let content =
"api_key sha384-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
assert!(
check(content).is_err(),
"sha384-prefixed hash labeled api_key must be blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_sha512_content_hash_labeled_as_auth() {
let content = "auth sha512-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUV";
assert!(
check(content).is_err(),
"sha512-prefixed hash labeled auth must be blocked; got {:?}",
scan(content)
);
}
#[test]
fn allows_uuid_with_no_trigger_within_window() {
let content =
"task 550e8400-e29b-41d4-a716-446655440000 was created and assigned to the team";
assert!(
check(content).is_ok(),
"UUID with no nearby trigger word must stay allowed; got {:?}",
scan(content)
);
}
#[test]
fn allows_area_id_uuid_near_authorized_substring() {
let content = "area_id: cfcea31d-6f50-4fd1-ad6d-5f160de1694c\n\n## Problem\nReduce Lion microkernel axioms. Converted authorized_write_requires_dominance from axiom to theorem.";
assert!(
check(content).is_ok(),
"internal area_id UUID near the 'authorized' substring \
(not a genuine 'auth' mention) must now pass; got {:?}",
scan(content)
);
}
#[test]
fn blocks_uuid_glued_to_assignment_equals() {
let content = "api_key=550e8400-e29b-41d4-a716-446655440000";
assert!(
check(content).is_err(),
"UUID glued via '=' to a trigger word must be blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_uuid_with_trailing_sentence_period() {
let content = "api_key 550e8400-e29b-41d4-a716-446655440000.";
assert!(
check(content).is_err(),
"UUID with a trailing sentence period near a trigger must be blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_uuid_wrapped_in_parens() {
let content = "api_key (550e8400-e29b-41d4-a716-446655440000)";
assert!(
check(content).is_err(),
"UUID wrapped in parens near a trigger must be blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_uuid_in_json_object() {
let content = "{\"api_key\":\"550e8400-e29b-41d4-a716-446655440000\"}";
assert!(
check(content).is_err(),
"UUID in a JSON-ish object near a trigger key must be blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_content_hash_glued_to_assignment_equals() {
let content = "secret=sha256-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq";
assert!(
check(content).is_err(),
"sha256-prefixed hash glued via '=' to a trigger word must be blocked; \
got {:?}",
scan(content)
);
}
#[test]
fn blocks_content_hash_with_trailing_sentence_period() {
let content = "secret sha256-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq.";
assert!(
check(content).is_err(),
"sha256-prefixed hash with a trailing period near a trigger must be \
blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_content_hash_wrapped_in_parens() {
let content = "secret (sha256-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq)";
assert!(
check(content).is_err(),
"sha256-prefixed hash wrapped in parens near a trigger must be blocked; \
got {:?}",
scan(content)
);
}
#[test]
fn blocks_content_hash_in_json_object() {
let content = "{\"secret\":\"sha256-ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopq\"}";
assert!(
check(content).is_err(),
"sha256-prefixed hash in a JSON-ish object near a trigger key must be \
blocked; got {:?}",
scan(content)
);
}
#[test]
fn allows_uuid_wrapped_in_parens_with_no_trigger_nearby() {
let content = "wrapper (550e8400-e29b-41d4-a716-446655440000) present";
assert!(
check(content).is_ok(),
"UUID wrapped in parens with no trigger word nearby must stay allowed; \
got {:?}",
scan(content)
);
}
#[test]
fn blocks_padded_content_hash_glued_to_assignment_with_trailing_period() {
let content = "secret=sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=.";
assert!(
check(content).is_err(),
"padded sha256 hash glued via '=' with a trailing period must be \
blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_padded_content_hash_in_json_object() {
let content = "{\"secret\":\"sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\"}";
assert!(
check(content).is_err(),
"padded sha256 hash in a JSON-ish object near a trigger key must be \
blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_uuid_when_json_label_itself_contains_colon() {
let content = "{\"api:key\":\"550e8400-e29b-41d4-a716-446655440000\"}";
assert!(
check(content).is_err(),
"UUID must be blocked even when the JSON label contains ':'; got {:?}",
scan(content)
);
}
#[test]
fn blocks_uuid_when_json_label_itself_contains_equals() {
let content = "{\"api=key\":\"550e8400-e29b-41d4-a716-446655440000\"}";
assert!(
check(content).is_err(),
"UUID must be blocked even when the JSON label contains '='; got {:?}",
scan(content)
);
}
#[test]
fn blocks_uuid_behind_doubled_assignment() {
let content = "api_key=label=550e8400-e29b-41d4-a716-446655440000"; assert!(
check(content).is_err(),
"UUID must be blocked behind a doubled assignment; got {:?}",
scan(content)
);
}
#[test]
fn blocks_padded_content_hash_behind_doubled_assignment_equals() {
let content = "secret=label=sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=.";
assert!(
check(content).is_err(),
"padded content hash must be blocked behind a doubled '=' assignment; \
got {:?}",
scan(content)
);
}
#[test]
fn blocks_padded_content_hash_behind_doubled_assignment_colon() {
let content = "secret:label=sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=.";
assert!(
check(content).is_err(),
"padded content hash must be blocked behind a doubled ':'+'=' \
assignment; got {:?}",
scan(content)
);
}
#[test]
fn allows_benign_url_with_scheme_and_path_separators() {
let content = "api_key endpoint=https://example.test/resource/for/testing";
assert!(
check(content).is_ok(),
"a benign URL near a trigger word must stay allowed; got {:?}",
scan(content)
);
}
#[test]
fn allows_trigger_substrings_inside_benign_path_slugs() {
let paths = [
"docs/_archive/adr_v0/ADR-051-cli-auth-and-kg-git-workflow.md",
"docs/platform/oauth-callback-docs-and-redirect-handling-v2.md",
"docs/research/author-attribution-and-collaboration-notes.md",
"docs/security/passwordless-authentication-overview-v3.md",
"docs/platform/private_keynote-authoring-guide-v2.md",
];
for path in paths {
assert!(
check(path).is_ok(),
"trigger substring inside a benign path slug must not make the path \
its own credential context: {path:?}, got {:?}",
scan(path)
);
}
}
#[test]
fn blocks_inline_auth_assignment_with_high_entropy_value() {
let content = "auth=Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; assert!(
check(content).is_err(),
"auth=<high-entropy-value> must still be blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_suffix_bearing_compound_credential_assignments() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let cases = [
format!("api_keyv2={opaque}"),
format!("access_keyv2={opaque}"),
format!("private_keyv2={opaque}"),
format!("API_KEYV2={opaque}"),
format!(r#"{{"private_keyv2":"{opaque}"}}"#),
];
for content in &cases {
assert!(
check(content).is_err(),
"suffix-bearing compound credential assignment must be blocked: \
{content:?}, got {:?}",
scan(content)
);
}
}
#[test]
fn blocks_spaced_suffix_bearing_compound_credential_assignments() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; for label in ["api_keyv2", "access_keyv2", "private_keyv2"] {
let cases = [
format!("{label} = {opaque}"),
format!("{label} : {opaque}"),
format!(r#"{{"{label}": "{opaque}"}}"#),
];
for content in &cases {
assert!(
check(content).is_err(),
"spaced suffix-bearing compound credential assignment must be \
blocked: {content:?}, got {:?}",
scan(content)
);
}
}
}
#[test]
fn blocks_suffix_bearing_compound_credentials_without_assignment_separator() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; for label in ["api_keyv2", "access_keyv2", "private_keyv2"] {
let cases = [format!("{label} {opaque}"), format!("{label}{opaque}")];
for content in &cases {
assert!(
check(content).is_err(),
"suffix-bearing compound credential without an assignment separator must be \
blocked: {content:?}, got {:?}",
scan(content)
);
assert!(
mask_secrets(content).contains(REDACTION_MARKER),
"shared secret masker must redact separator-free compound credential: \
{content:?}"
);
}
}
let prefixed = "xapi_keyv2=Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; assert!(
check(prefixed).is_err(),
"prefix-bearing compound credential assignment must be blocked: \
{prefixed:?}, got {:?}",
scan(prefixed)
);
}
#[test]
fn allows_authorized_and_authentication_prose_near_uuid() {
let cases = [
"authorized_write_requires_dominance was converted from axiom to theorem, id 550e8400-e29b-41d4-a716-446655440000",
"authentication flow diagram lives at 550e8400-e29b-41d4-a716-446655440000",
];
for content in cases {
assert!(
check(content).is_ok(),
"'authorized'/'authentication' substring must not trigger the \
entropy heuristic: {content:?}, got {:?}",
scan(content)
);
}
}
#[test]
fn allows_turkey_monkey_keyword_prose_near_uuid() {
let cases = [
"the turkey and monkey story references id 550e8400-e29b-41d4-a716-446655440000",
"keyword research doc: 550e8400-e29b-41d4-a716-446655440000",
];
for content in cases {
assert!(
check(content).is_ok(),
"'turkey'/'monkey'/'keyword' substring must not trigger the \
entropy heuristic: {content:?}, got {:?}",
scan(content)
);
}
}
#[test]
fn blocks_opaque_tokens_near_standalone_trigger_words() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let cases = [
format!("auth header {opaque}"),
format!("the key is {opaque}"),
format!("secret value: {opaque}"),
];
for content in &cases {
assert!(
check(content).is_err(),
"a genuine standalone trigger word must still block: {content:?}, \
got {:?}",
scan(content)
);
}
}
#[test]
fn blocks_workspace_artifact_path_near_standalone_secret() {
let content = "writing up the secret gate false positive repro: \
.workspace/20260101/fix-secret-gate-trigger-false-positive/MEASUREMENT_REPORT.md";
assert!(
check(content).is_err(),
"accepted FP: workspace artifact path near a \
genuine standalone 'secret' mention is still blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_archive_doc_path_near_standalone_secret() {
let content =
"secret scanner archive notes: docs/_archive/ADR051-TenantEncryption-v2Notes.md";
assert!(
check(content).is_err(),
"accepted FP: archive doc path near a genuine \
standalone 'secret' mention is still blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_absolute_path_near_standalone_auth() {
let content = "the auth scanner flagged this file: /home/user/projects/workspace/SessionNotes20260107/AuthGateFollowup2.md";
assert!(
check(content).is_err(),
"accepted FP: absolute path near a genuine \
standalone 'auth' mention is still blocked; got {:?}",
scan(content)
);
}
#[test]
fn blocks_assignment_shaped_credential_disguised_as_path_near_api_key() {
let content = "api_key=/home/user/workspaces/2026/topic-name-example/SECRET_VALUE_HERE.md";
assert!(
check(content).is_err(),
"assignment-shaped credential disguised as a path must still be \
blocked: {content:?}, got {:?}",
scan(content)
);
}
#[test]
fn blocks_separator_split_secret_access_key_compound() {
let content = "secret_access_key abcdefghij/klmnopqrst/uvwxyzabcd/efghijk.md";
assert!(
check(content).is_err(),
"secret_access_key bypass shape must still be blocked: {content:?}, \
got {:?}",
scan(content)
);
}
#[test]
fn blocks_secret_key_assignment_when_underscore_bounds_trigger() {
let content = "SECRET_KEY=dGhpc2lzYXNlY3JldGtleXZhbHVlMTIzNDU2Nzg5MA=="; assert!(
check(content).is_err(),
"SECRET_KEY=<value> (Django/Flask-style config) must still be \
blocked: {content:?}, got {:?}",
scan(content)
);
}
#[test]
fn blocks_auth_token_assignment_when_underscore_bounds_trigger() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let content = format!("auth_token={opaque}");
assert!(
check(&content).is_err(),
"auth_token=<value> must still be blocked: {content:?}, got {:?}",
scan(&content)
);
}
#[test]
fn blocks_underscore_joined_session_secret_and_signing_key_compounds() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let cases = [
format!("session_secret_{opaque}"),
format!("signing_key={opaque}"),
];
for content in &cases {
assert!(
check(content).is_err(),
"underscore-joined credential compound must still be blocked: \
{content:?}, got {:?}",
scan(content)
);
}
}
#[test]
fn allows_letter_joined_trigger_substrings_in_benign_prose() {
let cases = [
"authorized_write_requires_dominance was converted from axiom to theorem, id 550e8400-e29b-41d4-a716-446655440000",
"authentication flow diagram lives at 550e8400-e29b-41d4-a716-446655440000",
"the turkey and monkey story references id 550e8400-e29b-41d4-a716-446655440000",
"keyword research doc: 550e8400-e29b-41d4-a716-446655440000",
];
for content in cases {
assert!(
check(content).is_ok(),
"letter-joined substring collision must stay exempt: \
{content:?}, got {:?}",
scan(content)
);
}
}
#[test]
fn block_message_carries_actionable_guidance() {
let fake = "AKIAFAKEKEY1234567890";
let m = scan(fake).unwrap();
let rendered = m.to_string();
assert!(
rendered.contains("real credential"),
"block message must carry actionable guidance: {rendered}"
);
}
#[test]
fn block_message_shape_guidance_mentions_rewording() {
let opaque = "Xk9mZ2vQpLrT8nJwYuAeHfBsDcGiONvMabcdef"; let content = format!("auth header {opaque}");
let m = scan(&content).unwrap();
let rendered = m.to_string();
assert!(
rendered.contains("reword") || rendered.contains("own line"),
"shape-based detector guidance must suggest rewording/splitting: {rendered}"
);
}
}
#[cfg(test)]
mod corpus_replay {
use super::*;
use rusqlite::{Connection, OpenFlags};
#[test]
#[ignore]
fn replay_against_corpus() {
let db_path = std::env::var("KHIVE_REPLAY_DB")
.expect("set KHIVE_REPLAY_DB=/path/to/khive.db to run the corpus replay (read-only)");
let conn = Connection::open_with_flags(
&db_path,
OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX,
)
.expect("open corpus DB read-only");
let mut total = 0usize;
let mut blocked = 0usize;
let mut samples: Vec<String> = Vec::new();
let mut collect = |sql: &str| {
let mut stmt = conn.prepare(sql).expect("prepare replay query");
let mut rows = stmt.query([]).expect("query replay rows");
while let Some(row) = rows.next().expect("read replay row") {
let content: Option<String> = row.get(0).unwrap_or(None);
let Some(content) = content else { continue };
if content.is_empty() {
continue;
}
total += 1;
if let Some(m) = scan(&content) {
blocked += 1;
if samples.len() < 30 {
samples.push(format!(
"{} :: {}",
m,
content.chars().take(160).collect::<String>()
));
}
}
}
};
collect("SELECT content FROM notes WHERE deleted_at IS NULL");
collect("SELECT description FROM entities WHERE deleted_at IS NULL");
eprintln!("corpus replay: {blocked}/{total} strings blocked");
for s in &samples {
eprintln!(" BLOCKED: {s}");
}
}
}