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    /// 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}