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 /// Honor in-band suppression directives (`cc-audit-disable`/`cc-audit-ignore`)
111 /// read from scanned content. Off by default: untrusted artifacts must not be
112 /// able to disable rules on themselves (issue #156).
113 pub allow_inline_suppression: bool,
114
115 // ============ Remote Scanning Options (v1.1.0) ============
116 /// Remote repository URL to scan.
117 pub remote: Option<String>,
118 /// Git reference to checkout (branch, tag, commit).
119 pub git_ref: Option<String>,
120 /// GitHub authentication token (also reads from GITHUB_TOKEN env var).
121 pub remote_auth: Option<String>,
122 /// Number of parallel clones for batch scanning.
123 pub parallel_clones: Option<usize>,
124 /// File containing list of repository URLs to scan.
125 pub remote_list: Option<String>,
126 /// Scan all repositories from awesome-claude-code.
127 pub awesome_claude_code: bool,
128
129 // ============ Badge Options (v1.1.0) ============
130 /// Generate a badge for the scan result.
131 pub badge: bool,
132 /// Badge format: "markdown", "html", "json".
133 pub badge_format: Option<String>,
134 /// Show summary only (useful for batch scanning).
135 pub summary: bool,
136
137 // ============ Client Scan Options (v1.1.0) ============
138 /// Scan all installed AI coding clients (Claude Code, Cursor, etc.).
139 pub all_clients: bool,
140 /// Specific client to scan: "claude-code", "cursor", "windsurf", "cline", "roo-code", "claude-desktop", "amazon-q".
141 pub client: Option<String>,
142
143 // ============ CVE Scan Options (v1.1.0) ============
144 /// Disable CVE vulnerability scanning.
145 pub no_cve_scan: bool,
146 /// Path to a custom CVE database (JSON).
147 pub cve_db: Option<String>,
148
149 // ============ SBOM Options (v1.2.0) ============
150 /// Generate SBOM (Software Bill of Materials).
151 pub sbom: bool,
152 /// SBOM output format: "cyclonedx", "spdx".
153 pub sbom_format: Option<String>,
154 /// Include npm dependencies in SBOM.
155 pub sbom_npm: bool,
156 /// Include Cargo dependencies in SBOM.
157 pub sbom_cargo: bool,
158}
159
160/// Watch mode configuration.
161#[derive(Debug, Clone, Serialize, Deserialize)]
162#[serde(default)]
163pub struct WatchConfig {
164 /// Debounce duration in milliseconds.
165 pub debounce_ms: u64,
166 /// Poll interval in milliseconds.
167 pub poll_interval_ms: u64,
168}
169
170impl Default for WatchConfig {
171 fn default() -> Self {
172 Self {
173 debounce_ms: 300,
174 poll_interval_ms: 500,
175 }
176 }
177}
178
179/// Baseline configuration for drift detection (rug pull prevention).
180#[derive(Debug, Clone, Default, Serialize, Deserialize)]
181#[serde(default)]
182pub struct BaselineConfig {
183 /// Create a baseline snapshot when scanning.
184 pub enabled: bool,
185 /// Check for drift against saved baseline.
186 pub check_drift: bool,
187 /// Path to save baseline to.
188 pub save_to: Option<String>,
189 /// Path to baseline file to compare against.
190 pub compare_with: Option<String>,
191}
192
193/// Text file detection configuration.
194#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(default)]
196pub struct TextFilesConfig {
197 /// File extensions that should be treated as text.
198 pub extensions: HashSet<String>,
199 /// Special file names that should be treated as text (without extension).
200 pub special_names: HashSet<String>,
201}
202
203impl Default for TextFilesConfig {
204 fn default() -> Self {
205 let extensions: HashSet<String> = [
206 // Markdown and text
207 "md",
208 "txt",
209 "rst",
210 // Configuration
211 "json",
212 "yaml",
213 "yml",
214 "toml",
215 "xml",
216 "ini",
217 "conf",
218 "cfg",
219 "env",
220 // Shell
221 "sh",
222 "bash",
223 "zsh",
224 "fish",
225 // Scripting
226 "py",
227 "rb",
228 "pl",
229 "pm",
230 "lua",
231 "r",
232 // Web
233 "js",
234 "ts",
235 "jsx",
236 "tsx",
237 "html",
238 "css",
239 "scss",
240 "sass",
241 "less",
242 // Systems
243 "rs",
244 "go",
245 "c",
246 "cpp",
247 "h",
248 "hpp",
249 "cc",
250 "cxx",
251 // JVM
252 "java",
253 "kt",
254 "kts",
255 "scala",
256 "clj",
257 "groovy",
258 // .NET
259 "cs",
260 "fs",
261 "vb",
262 // Mobile
263 "swift",
264 "m",
265 "mm",
266 // Other languages
267 "php",
268 "ex",
269 "exs",
270 "hs",
271 "ml",
272 "vim",
273 "el",
274 "lisp",
275 // Docker
276 "dockerfile",
277 // Build
278 "makefile",
279 "cmake",
280 "gradle",
281 ]
282 .into_iter()
283 .map(String::from)
284 .collect();
285
286 let special_names: HashSet<String> = [
287 "Dockerfile",
288 "Makefile",
289 "Rakefile",
290 "Gemfile",
291 "Podfile",
292 "Vagrantfile",
293 "Procfile",
294 "LICENSE",
295 "README",
296 "CHANGELOG",
297 "CONTRIBUTING",
298 "AUTHORS",
299 "CMakeLists.txt",
300 "Justfile",
301 ]
302 .into_iter()
303 .map(String::from)
304 .collect();
305
306 Self {
307 extensions,
308 special_names,
309 }
310 }
311}
312
313impl TextFilesConfig {
314 /// Check if a path should be treated as a text file.
315 pub fn is_text_file(&self, path: &Path) -> bool {
316 // Check by extension
317 if let Some(ext) = path.extension().and_then(|e| e.to_str())
318 && self.extensions.contains(&ext.to_lowercase())
319 {
320 return true;
321 }
322
323 // Check by filename (case-insensitive for special names)
324 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
325 // Check exact match first
326 if self.special_names.contains(name) {
327 return true;
328 }
329 // Check case-insensitive match
330 let name_lower = name.to_lowercase();
331 if self
332 .special_names
333 .iter()
334 .any(|n| n.to_lowercase() == name_lower)
335 {
336 return true;
337 }
338 }
339
340 false
341 }
342}
343
344/// Ignore configuration for scanning.
345///
346/// Uses glob patterns to determine which paths to ignore during scanning.
347///
348/// # Glob Pattern Syntax
349///
350/// - `*` - matches any sequence of characters except path separators
351/// - `**` - matches any sequence of characters including path separators
352/// - `?` - matches any single character
353/// - `{a,b}` - matches either `a` or `b`
354/// - `[abc]` - matches any character in the set
355/// - `[!abc]` - matches any character not in the set
356///
357/// # Examples
358///
359/// ```yaml
360/// ignore:
361/// patterns:
362/// - "**/node_modules/**" # Ignore node_modules anywhere
363/// - "**/target/**" # Ignore Rust build artifacts
364/// - "**/.git/**" # Ignore git directories
365/// - "**/*.{log,tmp,bak}" # Ignore log, tmp, and bak files
366/// - "**/test{,s}/**" # Ignore test or tests directories
367/// ```
368#[derive(Debug, Clone, Default, Serialize, Deserialize)]
369#[serde(default)]
370pub struct IgnoreConfig {
371 /// Glob patterns to ignore (e.g., ["**/node_modules/**", "**/target/**", "**/.git/**"]).
372 /// Each pattern is matched against the full path of the file.
373 pub patterns: Vec<String>,
374}