use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SensitiveCategory {
Secret,
Pii,
Internal,
Custom(String),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RedactionStrategy {
Mask,
Fingerprint,
Drop,
Tokenize,
Partial,
TypeLabel,
Keep,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Span {
pub start: usize,
pub end: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SensitiveDataFinding {
pub id: String,
pub category: SensitiveCategory,
pub data_type: String,
pub confidence: f32,
pub span: Span,
pub preview: String,
pub detector: String,
pub recommended_action: RedactionStrategy,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Redaction {
pub finding_id: String,
pub strategy: RedactionStrategy,
pub original_span: Span,
pub replacement: String,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ProcessingStats {
pub input_length: usize,
pub output_length: usize,
pub findings_count: usize,
pub redactions_count: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SanitizationResult {
pub sanitized: String,
pub was_redacted: bool,
pub findings: Vec<SensitiveDataFinding>,
pub redactions: Vec<Redaction>,
pub stats: ProcessingStats,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CategoryConfig {
pub secrets: bool,
pub pii: bool,
pub internal: bool,
}
impl Default for CategoryConfig {
fn default() -> Self {
Self {
secrets: true,
pii: true,
internal: true,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EntropyConfig {
pub enabled: bool,
pub threshold: f64,
pub min_token_len: usize,
}
impl Default for EntropyConfig {
fn default() -> Self {
Self {
enabled: true,
threshold: 4.5,
min_token_len: 16,
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct AllowlistConfig {
pub exact: Vec<String>,
pub patterns: Vec<String>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct DenylistConfig {
pub exact: Vec<String>,
pub patterns: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OutputSanitizerConfig {
pub categories: CategoryConfig,
pub redaction_strategies: HashMap<SensitiveCategory, RedactionStrategy>,
pub entropy: EntropyConfig,
pub allowlist: AllowlistConfig,
pub denylist: DenylistConfig,
pub max_input_bytes: usize,
pub include_findings: bool,
}
impl Default for OutputSanitizerConfig {
fn default() -> Self {
let mut redaction_strategies = HashMap::new();
redaction_strategies.insert(SensitiveCategory::Secret, RedactionStrategy::Mask);
redaction_strategies.insert(SensitiveCategory::Pii, RedactionStrategy::Partial);
redaction_strategies.insert(SensitiveCategory::Internal, RedactionStrategy::TypeLabel);
Self {
categories: CategoryConfig::default(),
redaction_strategies,
entropy: EntropyConfig::default(),
allowlist: AllowlistConfig::default(),
denylist: DenylistConfig::default(),
max_input_bytes: 1_000_000,
include_findings: true,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum OutputSanitizerConfigError {
#[error("invalid {list_name} regex `{pattern}`: {source}")]
InvalidPattern {
list_name: &'static str,
pattern: String,
#[source]
source: regex::Error,
},
}
#[derive(Debug, Clone)]
pub struct SanitizedValue {
pub value: serde_json::Value,
pub findings: Vec<SensitiveDataFinding>,
pub redactions: Vec<Redaction>,
pub was_redacted: bool,
}