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