Implement chrs-shhh: Secrets Sentinel for redaction of API keys and tokens
This commit is contained in:
11
chrs-shhh/Cargo.toml
Normal file
11
chrs-shhh/Cargo.toml
Normal 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
82
chrs-shhh/src/lib.rs
Normal 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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user