cargo_context_core/scrub/
config.rs1use serde::Deserialize;
8
9use crate::scrub::Pattern;
10use crate::scrub::entropy::EntropyConfigRaw;
11use crate::scrub::paths::PathRulesRaw;
12
13#[derive(Debug, Clone, Default, Deserialize)]
15#[serde(deny_unknown_fields, default)]
16pub struct ScrubConfig {
17 pub version: u32,
18 pub builtins: BuiltinsMode,
19 pub disable_builtins: Vec<String>,
20 pub patterns: Vec<Pattern>,
21 pub entropy: EntropyConfigRaw,
22 pub paths: PathRulesRaw,
23 pub allowlist: Vec<AllowlistEntry>,
24 pub report: ReportConfig,
25}
26
27#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum BuiltinsMode {
31 #[default]
33 Extend,
34 Replace,
36 Disable,
40}
41
42#[derive(Debug, Clone, Default, Deserialize)]
44pub struct AllowlistEntry {
45 #[serde(default)]
46 pub exact: Option<String>,
47 #[serde(default)]
48 pub regex: Option<String>,
49}
50
51#[derive(Debug, Clone, Default, Deserialize)]
52pub struct ReportConfig {
53 #[serde(default)]
54 pub stderr_summary: bool,
55 #[serde(default)]
56 pub fail_on_match: bool,
57 #[serde(default)]
58 pub log_file: Option<std::path::PathBuf>,
59 #[serde(default)]
63 pub max_entries: Option<usize>,
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn parses_minimal_config() {
72 let yaml = "version: 1";
73 let cfg: ScrubConfig = serde_yaml::from_str(yaml).unwrap();
74 assert_eq!(cfg.version, 1);
75 assert_eq!(cfg.builtins, BuiltinsMode::Extend);
76 }
77
78 #[test]
79 fn parses_full_example() {
80 let yaml = r#"
82version: 1
83builtins: extend
84disable_builtins:
85 - jwt
86patterns:
87 - id: acme
88 regex: 'ACME_[A-Z0-9]{32}'
89 category: api_key
90 severity: high
91entropy:
92 enabled: true
93 min_length: 20
94 threshold: 4.5
95 context_keys:
96 - key
97 - secret
98paths:
99 redact_whole:
100 - "**/.env"
101 exclude:
102 - "**/test_fixtures/**"
103allowlist:
104 - exact: "sk-ant-api03-PUBLIC-DEMO"
105 - regex: '^AKIAEXAMPLE[0-9]+$'
106report:
107 stderr_summary: true
108 fail_on_match: false
109"#;
110 let cfg: ScrubConfig = serde_yaml::from_str(yaml).unwrap();
111 assert_eq!(cfg.version, 1);
112 assert_eq!(cfg.disable_builtins, vec!["jwt".to_string()]);
113 assert_eq!(cfg.patterns.len(), 1);
114 assert_eq!(cfg.patterns[0].id, "acme");
115 assert_eq!(cfg.entropy.context_keys.len(), 2);
116 assert_eq!(cfg.paths.redact_whole.len(), 1);
117 assert_eq!(cfg.allowlist.len(), 2);
118 assert!(cfg.report.stderr_summary);
119 }
120
121 #[test]
122 fn unknown_fields_are_rejected() {
123 let yaml = "version: 1\nnonsense_field: true\n";
124 let err = serde_yaml::from_str::<ScrubConfig>(yaml);
125 assert!(err.is_err(), "unknown top-level fields should reject");
126 }
127
128 #[test]
129 fn builtins_modes_parse() {
130 for (s, expected) in [
131 ("builtins: extend", BuiltinsMode::Extend),
132 ("builtins: replace", BuiltinsMode::Replace),
133 ("builtins: disable", BuiltinsMode::Disable),
134 ] {
135 let cfg: ScrubConfig = serde_yaml::from_str(s).unwrap();
136 assert_eq!(cfg.builtins, expected);
137 }
138 }
139}