1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SecurityAnalysisConfig {
10 pub include_low_severity: bool,
12 pub include_info_level: bool,
13
14 pub check_secrets: bool,
16 pub check_code_patterns: bool,
17 pub check_infrastructure: bool,
18 pub check_compliance: bool,
19
20 pub javascript_enabled: bool,
22 pub python_enabled: bool,
23 pub rust_enabled: bool,
24
25 pub frameworks_to_check: Vec<String>,
27
28 pub ignore_patterns: Vec<String>,
30 pub include_patterns: Vec<String>,
31
32 pub skip_gitignored_files: bool,
34 pub downgrade_gitignored_severity: bool,
35 pub check_git_history: bool,
36
37 pub check_env_files: bool,
39 pub warn_on_public_env_vars: bool,
40 pub sensitive_env_keywords: Vec<String>,
41
42 pub check_package_json: bool,
44 pub check_node_modules: bool,
45 pub framework_env_prefixes: Vec<String>,
46
47 pub max_findings_per_file: Option<usize>,
49 pub deduplicate_findings: bool,
50 pub group_by_severity: bool,
51
52 pub max_file_size_mb: Option<usize>,
54 pub parallel_analysis: bool,
55 pub analysis_timeout_seconds: Option<u64>,
56}
57
58impl Default for SecurityAnalysisConfig {
59 fn default() -> Self {
60 Self {
61 include_low_severity: false,
63 include_info_level: false,
64
65 check_secrets: true,
67 check_code_patterns: true,
68 check_infrastructure: true,
69 check_compliance: false, javascript_enabled: true,
73 python_enabled: true,
74 rust_enabled: true,
75
76 frameworks_to_check: vec![
78 "React".to_string(),
79 "Vue".to_string(),
80 "Angular".to_string(),
81 "Next.js".to_string(),
82 "Vite".to_string(),
83 "Express".to_string(),
84 "Django".to_string(),
85 "Spring Boot".to_string(),
86 ],
87
88 ignore_patterns: vec![
90 "node_modules".to_string(),
92 ".git".to_string(),
93 "target".to_string(),
94 "build".to_string(),
95 ".next".to_string(),
96 "coverage".to_string(),
97 "dist".to_string(),
98 ".nuxt".to_string(),
99 ".output".to_string(),
100 ".vercel".to_string(),
101 ".netlify".to_string(),
102
103 "venv/".to_string(),
105 ".venv/".to_string(),
106
107 "*.min.js".to_string(),
109 "*.min.css".to_string(),
110 "*.bundle.js".to_string(),
111 "*.bundle.css".to_string(),
112 "*.chunk.js".to_string(),
113 "*.vendor.js".to_string(),
114 "*.map".to_string(),
115
116 "*.lock".to_string(),
118 "*.lockb".to_string(),
119 "yarn.lock".to_string(),
120 "package-lock.json".to_string(),
121 "pnpm-lock.yaml".to_string(),
122 "bun.lockb".to_string(),
123 "cargo.lock".to_string(),
124 "go.sum".to_string(),
125 "poetry.lock".to_string(),
126 "composer.lock".to_string(),
127 "gemfile.lock".to_string(),
128
129 "*.jpg".to_string(),
131 "*.jpeg".to_string(),
132 "*.png".to_string(),
133 "*.gif".to_string(),
134 "*.bmp".to_string(),
135 "*.svg".to_string(),
136 "*.ico".to_string(),
137 "*.webp".to_string(),
138 "*.tiff".to_string(),
139 "*.mp3".to_string(),
140 "*.mp4".to_string(),
141 "*.avi".to_string(),
142 "*.mov".to_string(),
143 "*.pdf".to_string(),
144 "*.ttf".to_string(),
145 "*.otf".to_string(),
146 "*.woff".to_string(),
147 "*.woff2".to_string(),
148 "*.eot".to_string(),
149
150 "*.wt".to_string(),
152 "*.cer".to_string(),
153 "*.jks".to_string(),
154
155 "*_sample.*".to_string(),
157 "*example*".to_string(),
158 "*test*".to_string(),
159 "*spec*".to_string(),
160 "*mock*".to_string(),
161 "*fixture*".to_string(),
162 "test/*".to_string(),
163 "tests/*".to_string(),
164 "__test__/*".to_string(),
165 "__tests__/*".to_string(),
166 "spec/*".to_string(),
167 "specs/*".to_string(),
168
169 "*.md".to_string(),
171 "*.txt".to_string(),
172 "*.rst".to_string(),
173 "docs/*".to_string(),
174 "documentation/*".to_string(),
175
176 ".vscode/*".to_string(),
178 ".idea/*".to_string(),
179 ".vs/*".to_string(),
180 "*.swp".to_string(),
181 "*.swo".to_string(),
182 ".DS_Store".to_string(),
183 "Thumbs.db".to_string(),
184
185 "*.d.ts".to_string(),
187 "*.generated.*".to_string(),
188 "*.auto.*".to_string(),
189
190 ".angular/*".to_string(),
192 ".svelte-kit/*".to_string(),
193 "storybook-static/*".to_string(),
194 ],
195 include_patterns: vec![], skip_gitignored_files: true,
199 downgrade_gitignored_severity: false,
200 check_git_history: false, check_env_files: true,
204 warn_on_public_env_vars: true,
205 sensitive_env_keywords: vec![
206 "SECRET".to_string(),
207 "KEY".to_string(),
208 "TOKEN".to_string(),
209 "PASSWORD".to_string(),
210 "PASS".to_string(),
211 "AUTH".to_string(),
212 "API".to_string(),
213 "PRIVATE".to_string(),
214 "CREDENTIAL".to_string(),
215 "CERT".to_string(),
216 "SSL".to_string(),
217 "TLS".to_string(),
218 "OAUTH".to_string(),
219 "CLIENT_SECRET".to_string(),
220 "ACCESS_TOKEN".to_string(),
221 "REFRESH_TOKEN".to_string(),
222 "DATABASE_URL".to_string(),
223 "DB_PASS".to_string(),
224 "STRIPE_SECRET".to_string(),
225 "AWS_SECRET".to_string(),
226 "FIREBASE_PRIVATE".to_string(),
227 ],
228
229 check_package_json: true,
231 check_node_modules: false, framework_env_prefixes: vec![
233 "REACT_APP_".to_string(),
234 "NEXT_PUBLIC_".to_string(),
235 "VITE_".to_string(),
236 "VUE_APP_".to_string(),
237 "EXPO_PUBLIC_".to_string(),
238 "NUXT_PUBLIC_".to_string(),
239 "GATSBY_".to_string(),
240 "STORYBOOK_".to_string(),
241 ],
242
243 max_findings_per_file: Some(50), deduplicate_findings: true,
246 group_by_severity: true,
247
248 max_file_size_mb: Some(10), parallel_analysis: true,
251 analysis_timeout_seconds: Some(300), }
253 }
254}
255
256impl SecurityAnalysisConfig {
257 pub fn for_javascript() -> Self {
259 let mut config = Self::default();
260 config.javascript_enabled = true;
261 config.python_enabled = false;
262 config.rust_enabled = false;
263 config.check_package_json = true;
264 config.frameworks_to_check = vec![
265 "React".to_string(),
266 "Vue".to_string(),
267 "Angular".to_string(),
268 "Next.js".to_string(),
269 "Vite".to_string(),
270 "Express".to_string(),
271 "Svelte".to_string(),
272 "Nuxt".to_string(),
273 ];
274 config
275 }
276
277 pub fn for_python() -> Self {
279 let mut config = Self::default();
280 config.javascript_enabled = false;
281 config.python_enabled = true;
282 config.rust_enabled = false;
283 config.check_package_json = false;
284 config.frameworks_to_check = vec![
285 "Django".to_string(),
286 "Flask".to_string(),
287 "FastAPI".to_string(),
288 "Tornado".to_string(),
289 ];
290 config
291 }
292
293 pub fn high_security() -> Self {
295 let mut config = Self::default();
296 config.include_low_severity = true;
297 config.include_info_level = true;
298 config.skip_gitignored_files = false; config.check_git_history = true;
300 config.warn_on_public_env_vars = true;
301 config.max_findings_per_file = None; config
303 }
304
305 pub fn fast_ci() -> Self {
307 let mut config = Self::default();
308 config.include_low_severity = false;
309 config.include_info_level = false;
310 config.check_compliance = false;
311 config.check_git_history = false;
312 config.parallel_analysis = true;
313 config.max_findings_per_file = Some(20); config.analysis_timeout_seconds = Some(120); config
316 }
317
318 pub fn should_analyze_file(&self, file_path: &std::path::Path) -> bool {
320 let file_path_str = file_path.to_string_lossy();
321 let file_name = file_path.file_name()
322 .and_then(|n| n.to_str())
323 .unwrap_or("");
324
325 for pattern in &self.ignore_patterns {
327 if self.matches_pattern(pattern, &file_path_str, file_name) {
328 return false;
329 }
330 }
331
332 if !self.include_patterns.is_empty() {
334 return self.include_patterns.iter().any(|pattern| {
335 self.matches_pattern(pattern, &file_path_str, file_name)
336 });
337 }
338
339 true
340 }
341
342 fn matches_pattern(&self, pattern: &str, file_path: &str, file_name: &str) -> bool {
344 if pattern.contains('*') {
345 glob::Pattern::new(pattern)
347 .map(|p| p.matches(file_path) || p.matches(file_name))
348 .unwrap_or(false)
349 } else {
350 file_path.contains(pattern) || file_name.contains(pattern)
352 }
353 }
354
355 pub fn is_sensitive_env_var(&self, var_name: &str) -> bool {
357 let var_upper = var_name.to_uppercase();
358 self.sensitive_env_keywords.iter()
359 .any(|keyword| var_upper.contains(keyword))
360 }
361
362 pub fn is_public_env_var(&self, var_name: &str) -> bool {
364 self.framework_env_prefixes.iter()
365 .any(|prefix| var_name.starts_with(prefix))
366 }
367
368 pub fn max_file_size_bytes(&self) -> Option<usize> {
370 self.max_file_size_mb.map(|mb| mb * 1024 * 1024)
371 }
372}
373
374#[derive(Debug, Clone, Copy)]
376pub enum SecurityConfigPreset {
377 Default,
379 JavaScript,
381 Python,
383 HighSecurity,
385 FastCI,
387}
388
389impl SecurityConfigPreset {
390 pub fn to_config(self) -> SecurityAnalysisConfig {
391 match self {
392 Self::Default => SecurityAnalysisConfig::default(),
393 Self::JavaScript => SecurityAnalysisConfig::for_javascript(),
394 Self::Python => SecurityAnalysisConfig::for_python(),
395 Self::HighSecurity => SecurityAnalysisConfig::high_security(),
396 Self::FastCI => SecurityAnalysisConfig::fast_ci(),
397 }
398 }
399}
400
401impl From<SecurityConfigPreset> for SecurityAnalysisConfig {
402 fn from(preset: SecurityConfigPreset) -> Self {
403 preset.to_config()
404 }
405}