cc_audit/config/types.rs
1//! Configuration type definitions.
2
3use crate::malware_db::MalwareSignature;
4use crate::rules::custom::YamlRule;
5use serde::{Deserialize, Serialize};
6use std::collections::HashSet;
7use std::path::Path;
8
9use super::severity::SeverityConfig;
10
11/// Main configuration structure for cc-audit.
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13#[serde(default)]
14pub struct Config {
15 /// Scan configuration (CLI options).
16 pub scan: ScanConfig,
17 /// Watch mode configuration.
18 pub watch: WatchConfig,
19 /// Text file detection configuration.
20 pub text_files: TextFilesConfig,
21 /// Ignore configuration for scanning.
22 pub ignore: IgnoreConfig,
23 /// Baseline configuration for drift detection.
24 #[serde(default)]
25 pub baseline: BaselineConfig,
26 /// Rule severity configuration (v0.5.0).
27 #[serde(default)]
28 pub severity: SeverityConfig,
29 /// Rule IDs to disable.
30 #[serde(default)]
31 pub disabled_rules: HashSet<String>,
32 /// Custom rules defined in config file.
33 #[serde(default)]
34 pub rules: Vec<YamlRule>,
35 /// Custom malware signatures defined in config file.
36 #[serde(default)]
37 pub malware_signatures: Vec<MalwareSignature>,
38}
39
40impl Config {
41 /// Get the effective set of disabled rules (merges severity.ignore and disabled_rules).
42 pub fn effective_disabled_rules(&self) -> HashSet<String> {
43 let mut disabled = self.disabled_rules.clone();
44 disabled.extend(self.severity.ignore.iter().cloned());
45 disabled
46 }
47
48 /// Check if a rule should be ignored based on both disabled_rules and severity.ignore.
49 pub fn is_rule_disabled(&self, rule_id: &str) -> bool {
50 self.disabled_rules.contains(rule_id) || self.severity.ignore.contains(rule_id)
51 }
52
53 /// Get the RuleSeverity for a rule, considering both severity config and disabled_rules.
54 pub fn get_rule_severity(&self, rule_id: &str) -> Option<crate::rules::RuleSeverity> {
55 if self.is_rule_disabled(rule_id) {
56 return None;
57 }
58 self.severity.get_rule_severity(rule_id)
59 }
60}
61
62/// Scan configuration (corresponds to CLI options).
63#[derive(Debug, Clone, Default, Serialize, Deserialize)]
64#[serde(default)]
65pub struct ScanConfig {
66 /// Output format: "terminal", "json", "sarif", "html", "markdown".
67 pub format: Option<String>,
68 /// Strict mode: show medium/low severity findings and treat warnings as errors.
69 pub strict: bool,
70 /// Scan type: "skill", "hook", "mcp", "command", "rules", "docker", "dependency", "subagent", "plugin".
71 pub scan_type: Option<String>,
72 /// Recursive scan.
73 pub recursive: bool,
74 /// CI mode: non-interactive output.
75 pub ci: bool,
76 /// Verbose output.
77 pub verbose: bool,
78 /// Minimum confidence level: "tentative", "firm", "certain".
79 pub min_confidence: Option<String>,
80 /// Skip comment lines when scanning.
81 pub skip_comments: bool,
82 /// Show fix hints in terminal output.
83 pub fix_hint: bool,
84 /// Use compact output format (disable friendly advice).
85 pub compact: bool,
86 /// Disable malware signature scanning.
87 pub no_malware_scan: bool,
88 /// Watch mode: continuously monitor files for changes.
89 pub watch: bool,
90 /// Path to a custom malware signatures database (JSON).
91 pub malware_db: Option<String>,
92 /// Path to a custom rules file (YAML format).
93 pub custom_rules: Option<String>,
94 /// Output file path (for HTML/JSON/SARIF output).
95 pub output: Option<String>,
96 /// Enable deep scan with deobfuscation.
97 pub deep_scan: bool,
98 /// Auto-fix issues (where possible).
99 pub fix: bool,
100 /// Preview auto-fix changes without applying them.
101 pub fix_dry_run: bool,
102 /// Warn-only mode: treat all findings as warnings (always exit 0).
103 pub warn_only: bool,
104 /// Minimum severity level to include: "critical", "high", "medium", "low".
105 pub min_severity: Option<String>,
106 /// Minimum rule severity to treat as errors: "error", "warn".
107 pub min_rule_severity: Option<String>,
108 /// Strict secrets mode: disable dummy key heuristics for test files.
109 pub strict_secrets: bool,
110
111 // ============ Remote Scanning Options (v1.1.0) ============
112 /// Remote repository URL to scan.
113 pub remote: Option<String>,
114 /// Git reference to checkout (branch, tag, commit).
115 pub git_ref: Option<String>,
116 /// GitHub authentication token (also reads from GITHUB_TOKEN env var).
117 pub remote_auth: Option<String>,
118 /// Number of parallel clones for batch scanning.
119 pub parallel_clones: Option<usize>,
120 /// File containing list of repository URLs to scan.
121 pub remote_list: Option<String>,
122 /// Scan all repositories from awesome-claude-code.
123 pub awesome_claude_code: bool,
124
125 // ============ Badge Options (v1.1.0) ============
126 /// Generate a badge for the scan result.
127 pub badge: bool,
128 /// Badge format: "markdown", "html", "json".
129 pub badge_format: Option<String>,
130 /// Show summary only (useful for batch scanning).
131 pub summary: bool,
132
133 // ============ Client Scan Options (v1.1.0) ============
134 /// Scan all installed AI coding clients (Claude Code, Cursor, etc.).
135 pub all_clients: bool,
136 /// Specific client to scan: "claude-code", "cursor", "windsurf", "cline", "roo-code", "claude-desktop", "amazon-q".
137 pub client: Option<String>,
138
139 // ============ CVE Scan Options (v1.1.0) ============
140 /// Disable CVE vulnerability scanning.
141 pub no_cve_scan: bool,
142 /// Path to a custom CVE database (JSON).
143 pub cve_db: Option<String>,
144
145 // ============ SBOM Options (v1.2.0) ============
146 /// Generate SBOM (Software Bill of Materials).
147 pub sbom: bool,
148 /// SBOM output format: "cyclonedx", "spdx".
149 pub sbom_format: Option<String>,
150 /// Include npm dependencies in SBOM.
151 pub sbom_npm: bool,
152 /// Include Cargo dependencies in SBOM.
153 pub sbom_cargo: bool,
154}
155
156/// Watch mode configuration.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(default)]
159pub struct WatchConfig {
160 /// Debounce duration in milliseconds.
161 pub debounce_ms: u64,
162 /// Poll interval in milliseconds.
163 pub poll_interval_ms: u64,
164}
165
166impl Default for WatchConfig {
167 fn default() -> Self {
168 Self {
169 debounce_ms: 300,
170 poll_interval_ms: 500,
171 }
172 }
173}
174
175/// Baseline configuration for drift detection (rug pull prevention).
176#[derive(Debug, Clone, Default, Serialize, Deserialize)]
177#[serde(default)]
178pub struct BaselineConfig {
179 /// Create a baseline snapshot when scanning.
180 pub enabled: bool,
181 /// Check for drift against saved baseline.
182 pub check_drift: bool,
183 /// Path to save baseline to.
184 pub save_to: Option<String>,
185 /// Path to baseline file to compare against.
186 pub compare_with: Option<String>,
187}
188
189/// Text file detection configuration.
190#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(default)]
192pub struct TextFilesConfig {
193 /// File extensions that should be treated as text.
194 pub extensions: HashSet<String>,
195 /// Special file names that should be treated as text (without extension).
196 pub special_names: HashSet<String>,
197}
198
199impl Default for TextFilesConfig {
200 fn default() -> Self {
201 let extensions: HashSet<String> = [
202 // Markdown and text
203 "md",
204 "txt",
205 "rst",
206 // Configuration
207 "json",
208 "yaml",
209 "yml",
210 "toml",
211 "xml",
212 "ini",
213 "conf",
214 "cfg",
215 "env",
216 // Shell
217 "sh",
218 "bash",
219 "zsh",
220 "fish",
221 // Scripting
222 "py",
223 "rb",
224 "pl",
225 "pm",
226 "lua",
227 "r",
228 // Web
229 "js",
230 "ts",
231 "jsx",
232 "tsx",
233 "html",
234 "css",
235 "scss",
236 "sass",
237 "less",
238 // Systems
239 "rs",
240 "go",
241 "c",
242 "cpp",
243 "h",
244 "hpp",
245 "cc",
246 "cxx",
247 // JVM
248 "java",
249 "kt",
250 "kts",
251 "scala",
252 "clj",
253 "groovy",
254 // .NET
255 "cs",
256 "fs",
257 "vb",
258 // Mobile
259 "swift",
260 "m",
261 "mm",
262 // Other languages
263 "php",
264 "ex",
265 "exs",
266 "hs",
267 "ml",
268 "vim",
269 "el",
270 "lisp",
271 // Docker
272 "dockerfile",
273 // Build
274 "makefile",
275 "cmake",
276 "gradle",
277 ]
278 .into_iter()
279 .map(String::from)
280 .collect();
281
282 let special_names: HashSet<String> = [
283 "Dockerfile",
284 "Makefile",
285 "Rakefile",
286 "Gemfile",
287 "Podfile",
288 "Vagrantfile",
289 "Procfile",
290 "LICENSE",
291 "README",
292 "CHANGELOG",
293 "CONTRIBUTING",
294 "AUTHORS",
295 "CMakeLists.txt",
296 "Justfile",
297 ]
298 .into_iter()
299 .map(String::from)
300 .collect();
301
302 Self {
303 extensions,
304 special_names,
305 }
306 }
307}
308
309impl TextFilesConfig {
310 /// Check if a path should be treated as a text file.
311 pub fn is_text_file(&self, path: &Path) -> bool {
312 // Check by extension
313 if let Some(ext) = path.extension().and_then(|e| e.to_str())
314 && self.extensions.contains(&ext.to_lowercase())
315 {
316 return true;
317 }
318
319 // Check by filename (case-insensitive for special names)
320 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
321 // Check exact match first
322 if self.special_names.contains(name) {
323 return true;
324 }
325 // Check case-insensitive match
326 let name_lower = name.to_lowercase();
327 if self
328 .special_names
329 .iter()
330 .any(|n| n.to_lowercase() == name_lower)
331 {
332 return true;
333 }
334 }
335
336 false
337 }
338}
339
340/// Ignore configuration for scanning.
341#[derive(Debug, Clone, Serialize, Deserialize)]
342#[serde(default)]
343pub struct IgnoreConfig {
344 /// Directories to ignore (e.g., ["node_modules", "target", ".git"]).
345 pub directories: HashSet<String>,
346 /// Glob patterns to ignore (e.g., ["*.log", "build/**"]).
347 pub patterns: Vec<String>,
348 /// Whether to include test directories in scan.
349 pub include_tests: bool,
350 /// Whether to include node_modules in scan.
351 pub include_node_modules: bool,
352 /// Whether to include vendor directories in scan.
353 pub include_vendor: bool,
354}
355
356impl Default for IgnoreConfig {
357 fn default() -> Self {
358 let directories: HashSet<String> = [
359 // Common build output directories
360 "target",
361 "dist",
362 "build",
363 "out",
364 // Package manager directories
365 "node_modules",
366 ".pnpm",
367 ".yarn",
368 // Version control
369 ".git",
370 ".svn",
371 ".hg",
372 // IDE directories
373 ".idea",
374 ".vscode",
375 // Cache directories
376 ".cache",
377 "__pycache__",
378 ".pytest_cache",
379 ".mypy_cache",
380 // Coverage directories
381 "coverage",
382 ".nyc_output",
383 ]
384 .into_iter()
385 .map(String::from)
386 .collect();
387
388 Self {
389 directories,
390 patterns: Vec::new(),
391 include_tests: false,
392 include_node_modules: false,
393 include_vendor: false,
394 }
395 }
396}