pub const MASK: &[u8] = b"***";
pub fn mask_secrets(data: &[u8], secrets: &[&[u8]]) -> Vec<u8> {
let mut out = data.to_vec();
for secret in secrets {
if secret.is_empty() {
continue;
}
out = replace_bytes(&out, secret, MASK);
}
out
}
fn replace_bytes(haystack: &[u8], needle: &[u8], rep: &[u8]) -> Vec<u8> {
if needle.is_empty() || needle.len() > haystack.len() {
return haystack.to_vec();
}
let mut out = Vec::with_capacity(haystack.len());
let mut i = 0;
while i < haystack.len() {
if i + needle.len() <= haystack.len() && &haystack[i..i + needle.len()] == needle {
out.extend_from_slice(rep);
i += needle.len();
} else {
out.push(haystack[i]);
i += 1;
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn masks_naive_occurrences() {
let out = mask_secrets(b"connecting with hunter2 now", &[b"hunter2"]);
assert_eq!(out, b"connecting with *** now");
assert!(!String::from_utf8_lossy(&out).contains("hunter2"));
}
#[test]
fn masks_multiple_secrets_and_repeats() {
let out = mask_secrets(b"a=AAA b=BBB a=AAA", &[b"AAA", b"BBB"]);
assert_eq!(out, b"a=*** b=*** a=***");
}
#[test]
fn empty_secret_is_ignored() {
let out = mask_secrets(b"untouched", &[b""]);
assert_eq!(out, b"untouched");
}
#[test]
fn does_not_catch_obfuscated_exfiltration() {
let out = mask_secrets(b"aHVudGVyMg==", &[b"hunter2"]);
assert_eq!(out, b"aHVudGVyMg==");
}
}