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