Skip to main content

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///
342/// Uses regex patterns to determine which paths to ignore during scanning.
343#[derive(Debug, Clone, Default, Serialize, Deserialize)]
344#[serde(default)]
345pub struct IgnoreConfig {
346    /// Regex patterns to ignore (e.g., ["node_modules", "target/", "\\.git"]).
347    /// Each pattern is matched against the full path of the file.
348    pub patterns: Vec<String>,
349}