Implement chrs-shhh: Secrets Sentinel for redaction of API keys and tokens

This commit is contained in:
anthonyrawlins
2026-03-03 17:39:27 +11:00
parent ce5c2238dd
commit 99def66866
2 changed files with 93 additions and 0 deletions

11
chrs-shhh/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "chrs-shhh"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
regex = "1.10"
lazy_static = "1.4"

82
chrs-shhh/src/lib.rs Normal file
View File

@@ -0,0 +1,82 @@
use regex::Regex;
use lazy_static::lazy_static;
pub struct RedactionRule {
pub name: String,
pub pattern: Regex,
pub replacement: String,
}
pub struct SecretSentinel {
rules: Vec<RedactionRule>,
}
lazy_static! {
static ref OPENAI_KEY: Regex = Regex::new(r"sk-[a-zA-Z0-9]{48}").unwrap();
static ref AWS_KEY: Regex = Regex::new(r"AKIA[0-9A-Z]{16}").unwrap();
static ref GENERIC_SECRET: Regex = Regex::new(r"(?i)(password|secret|key|token)\s*[:=]\s*[^\s]+").unwrap();
}
impl SecretSentinel {
pub fn new_default() -> Self {
let rules = vec![
RedactionRule {
name: "OpenAI API Key".into(),
pattern: OPENAI_KEY.clone(),
replacement: "[REDACTED OPENAI KEY]".into(),
},
RedactionRule {
name: "AWS Access Key".into(),
pattern: AWS_KEY.clone(),
replacement: "[REDACTED AWS KEY]".into(),
},
RedactionRule {
name: "Generic Secret".into(),
pattern: GENERIC_SECRET.clone(),
replacement: "$1: [REDACTED]".into(),
},
];
Self { rules }
}
pub fn scrub_text(&self, input: &str) -> String {
let mut scrubbed = input.to_string();
for rule in &self.rules {
scrubbed = rule.pattern.replace_all(&scrubbed, &rule.replacement).to_string();
}
scrubbed
}
pub fn contains_secrets(&self, input: &str) -> bool {
self.rules.iter().any(|rule| rule.pattern.is_match(input))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scrub_openai_key() {
let sentinel = SecretSentinel::new_default();
let input = "My key is sk-1234567890abcdef1234567890abcdef1234567890abcdef";
let output = sentinel.scrub_text(input);
assert!(output.contains("[REDACTED OPENAI KEY]"));
assert!(!output.contains("sk-1234567890"));
}
#[test]
fn test_scrub_generic_password() {
let sentinel = SecretSentinel::new_default();
let input = "login with password: my-secret-password now";
let output = sentinel.scrub_text(input);
assert!(output.contains("password: [REDACTED]"));
}
#[test]
fn test_contains_secrets() {
let sentinel = SecretSentinel::new_default();
assert!(sentinel.contains_secrets("AKIAIOSFODNN7EXAMPLE"));
assert!(!sentinel.contains_secrets("nothing sensitive here"));
}
}