Skip to main content

assay_core/render_safety/
rules.rs

1//! Curated secret/PII shape rules for render-side redaction (MCP01a).
2//!
3//! Patterns mirror the capture-side redactor (`assay-runner-core::redact`) and the Plimsoll detector
4//! (`plimsoll/secrets.py`) so the three layers share one rule vocabulary. High-signal, low false
5//! positive: no generic entropy scan (sha256 digests / content ids are legitimately high-entropy in
6//! evidence and must survive). A match is replaced by a value-free `<redacted:RULE>` placeholder; the
7//! matched value is never emitted.
8
9use lazy_static::lazy_static;
10use regex::Regex;
11
12/// `(rule name, class, compiled pattern)`. `class` is "secret" or "pii" (drives leak accounting).
13pub struct Rule {
14    pub name: &'static str,
15    pub class: &'static str,
16    pub re: Regex,
17}
18
19lazy_static! {
20    pub static ref RULES: Vec<Rule> = vec![
21        rule(
22            "aws-access-key-id",
23            "secret",
24            r"\b(?:AKIA|ASIA)[0-9A-Z]{16}\b"
25        ),
26        rule("github-token", "secret", r"\bgh[pousr]_[A-Za-z0-9]{36,}\b"),
27        rule(
28            "openai-key",
29            "secret",
30            r"\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b"
31        ),
32        rule("slack-token", "secret", r"\bxox[baprs]-[A-Za-z0-9-]{10,}\b"),
33        rule("google-api-key", "secret", r"\bAIza[0-9A-Za-z_-]{35}\b"),
34        rule(
35            "stripe-key",
36            "secret",
37            r"\b[sp]k_(?:live|test)_[0-9A-Za-z]{16,}\b"
38        ),
39        rule(
40            "private-key-pem",
41            "secret",
42            r"-----BEGIN (?:[A-Z ]*)PRIVATE KEY-----"
43        ),
44        rule(
45            "jwt",
46            "secret",
47            r"\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b",
48        ),
49        rule(
50            "bearer-token",
51            "secret",
52            r"(?i)\bbearer\s+[A-Za-z0-9._~+/-]{20,}=*"
53        ),
54        rule(
55            "sensitive-query-param",
56            "secret",
57            r#"(?i)\b(?:access[_-]?token|refresh[_-]?token|sig|signature)=[^&\s#"']{6,}"#,
58        ),
59        rule(
60            "credential-assignment",
61            "secret",
62            r#"(?i)\b(?:api[_-]?key|secret|token|password|passwd|access[_-]?key|client[_-]?secret)\b\s*[=:]\s*[^\s'"]{6,}"#,
63        ),
64        rule("email", "pii", r"[\w.+-]+@[\w-]+\.[\w.]+"),
65        rule("slack-user-id", "pii", r"\b[UW][A-Z0-9]{8,10}\b"),
66    ];
67}
68
69fn rule(name: &'static str, class: &'static str, pattern: &str) -> Rule {
70    Rule {
71        name,
72        class,
73        re: Regex::new(pattern).expect("render-safety rule pattern is a valid regex"),
74    }
75}