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}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn parses_minimal_config() {
67 let yaml = "version: 1";
68 let cfg: ScrubConfig = serde_yaml::from_str(yaml).unwrap();
69 assert_eq!(cfg.version, 1);
70 assert_eq!(cfg.builtins, BuiltinsMode::Extend);
71 }
72
73 #[test]
74 fn parses_full_example() {
75 let yaml = r#"
77version: 1
78builtins: extend
79disable_builtins:
80 - jwt
81patterns:
82 - id: acme
83 regex: 'ACME_[A-Z0-9]{32}'
84 category: api_key
85 severity: high
86entropy:
87 enabled: true
88 min_length: 20
89 threshold: 4.5
90 context_keys:
91 - key
92 - secret
93paths:
94 redact_whole:
95 - "**/.env"
96 exclude:
97 - "**/test_fixtures/**"
98allowlist:
99 - exact: "sk-ant-api03-PUBLIC-DEMO"
100 - regex: '^AKIAEXAMPLE[0-9]+$'
101report:
102 stderr_summary: true
103 fail_on_match: false
104"#;
105 let cfg: ScrubConfig = serde_yaml::from_str(yaml).unwrap();
106 assert_eq!(cfg.version, 1);
107 assert_eq!(cfg.disable_builtins, vec!["jwt".to_string()]);
108 assert_eq!(cfg.patterns.len(), 1);
109 assert_eq!(cfg.patterns[0].id, "acme");
110 assert_eq!(cfg.entropy.context_keys.len(), 2);
111 assert_eq!(cfg.paths.redact_whole.len(), 1);
112 assert_eq!(cfg.allowlist.len(), 2);
113 assert!(cfg.report.stderr_summary);
114 }
115
116 #[test]
117 fn unknown_fields_are_rejected() {
118 let yaml = "version: 1\nnonsense_field: true\n";
119 let err = serde_yaml::from_str::<ScrubConfig>(yaml);
120 assert!(err.is_err(), "unknown top-level fields should reject");
121 }
122
123 #[test]
124 fn builtins_modes_parse() {
125 for (s, expected) in [
126 ("builtins: extend", BuiltinsMode::Extend),
127 ("builtins: replace", BuiltinsMode::Replace),
128 ("builtins: disable", BuiltinsMode::Disable),
129 ] {
130 let cfg: ScrubConfig = serde_yaml::from_str(s).unwrap();
131 assert_eq!(cfg.builtins, expected);
132 }
133 }
134}