syncable_cli/analyzer/security/
config.rs1use 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(),
91 ".git".to_string(),
92 "target".to_string(),
93 "build".to_string(),
94 ".next".to_string(),
95 "coverage".to_string(),
96 "dist".to_string(),
97 "*.min.js".to_string(),
98 "*.bundle.js".to_string(),
99 "*.map".to_string(),
100 "*.lock".to_string(),
101 "*_sample.*".to_string(),
102 "*example*".to_string(),
103 "*test*".to_string(),
104 "*spec*".to_string(),
105 "*mock*".to_string(),
106 "*.d.ts".to_string(), ],
108 include_patterns: vec![], skip_gitignored_files: true,
112 downgrade_gitignored_severity: false,
113 check_git_history: false, check_env_files: true,
117 warn_on_public_env_vars: true,
118 sensitive_env_keywords: vec![
119 "SECRET".to_string(),
120 "KEY".to_string(),
121 "TOKEN".to_string(),
122 "PASSWORD".to_string(),
123 "PASS".to_string(),
124 "AUTH".to_string(),
125 "API".to_string(),
126 "PRIVATE".to_string(),
127 "CREDENTIAL".to_string(),
128 "CERT".to_string(),
129 "SSL".to_string(),
130 "TLS".to_string(),
131 "OAUTH".to_string(),
132 "CLIENT_SECRET".to_string(),
133 "ACCESS_TOKEN".to_string(),
134 "REFRESH_TOKEN".to_string(),
135 "DATABASE_URL".to_string(),
136 "DB_PASS".to_string(),
137 "STRIPE_SECRET".to_string(),
138 "AWS_SECRET".to_string(),
139 "FIREBASE_PRIVATE".to_string(),
140 ],
141
142 check_package_json: true,
144 check_node_modules: false, framework_env_prefixes: vec![
146 "REACT_APP_".to_string(),
147 "NEXT_PUBLIC_".to_string(),
148 "VITE_".to_string(),
149 "VUE_APP_".to_string(),
150 "EXPO_PUBLIC_".to_string(),
151 "NUXT_PUBLIC_".to_string(),
152 "GATSBY_".to_string(),
153 "STORYBOOK_".to_string(),
154 ],
155
156 max_findings_per_file: Some(50), deduplicate_findings: true,
159 group_by_severity: true,
160
161 max_file_size_mb: Some(10), parallel_analysis: true,
164 analysis_timeout_seconds: Some(300), }
166 }
167}
168
169impl SecurityAnalysisConfig {
170 pub fn for_javascript() -> Self {
172 let mut config = Self::default();
173 config.javascript_enabled = true;
174 config.python_enabled = false;
175 config.rust_enabled = false;
176 config.check_package_json = true;
177 config.frameworks_to_check = vec![
178 "React".to_string(),
179 "Vue".to_string(),
180 "Angular".to_string(),
181 "Next.js".to_string(),
182 "Vite".to_string(),
183 "Express".to_string(),
184 "Svelte".to_string(),
185 "Nuxt".to_string(),
186 ];
187 config
188 }
189
190 pub fn for_python() -> Self {
192 let mut config = Self::default();
193 config.javascript_enabled = false;
194 config.python_enabled = true;
195 config.rust_enabled = false;
196 config.check_package_json = false;
197 config.frameworks_to_check = vec![
198 "Django".to_string(),
199 "Flask".to_string(),
200 "FastAPI".to_string(),
201 "Tornado".to_string(),
202 ];
203 config
204 }
205
206 pub fn high_security() -> Self {
208 let mut config = Self::default();
209 config.include_low_severity = true;
210 config.include_info_level = true;
211 config.skip_gitignored_files = false; config.check_git_history = true;
213 config.warn_on_public_env_vars = true;
214 config.max_findings_per_file = None; config
216 }
217
218 pub fn fast_ci() -> Self {
220 let mut config = Self::default();
221 config.include_low_severity = false;
222 config.include_info_level = false;
223 config.check_compliance = false;
224 config.check_git_history = false;
225 config.parallel_analysis = true;
226 config.max_findings_per_file = Some(20); config.analysis_timeout_seconds = Some(120); config
229 }
230
231 pub fn should_analyze_file(&self, file_path: &std::path::Path) -> bool {
233 let file_path_str = file_path.to_string_lossy();
234 let file_name = file_path.file_name()
235 .and_then(|n| n.to_str())
236 .unwrap_or("");
237
238 for pattern in &self.ignore_patterns {
240 if self.matches_pattern(pattern, &file_path_str, file_name) {
241 return false;
242 }
243 }
244
245 if !self.include_patterns.is_empty() {
247 return self.include_patterns.iter().any(|pattern| {
248 self.matches_pattern(pattern, &file_path_str, file_name)
249 });
250 }
251
252 true
253 }
254
255 fn matches_pattern(&self, pattern: &str, file_path: &str, file_name: &str) -> bool {
257 if pattern.contains('*') {
258 glob::Pattern::new(pattern)
260 .map(|p| p.matches(file_path) || p.matches(file_name))
261 .unwrap_or(false)
262 } else {
263 file_path.contains(pattern) || file_name.contains(pattern)
265 }
266 }
267
268 pub fn is_sensitive_env_var(&self, var_name: &str) -> bool {
270 let var_upper = var_name.to_uppercase();
271 self.sensitive_env_keywords.iter()
272 .any(|keyword| var_upper.contains(keyword))
273 }
274
275 pub fn is_public_env_var(&self, var_name: &str) -> bool {
277 self.framework_env_prefixes.iter()
278 .any(|prefix| var_name.starts_with(prefix))
279 }
280
281 pub fn max_file_size_bytes(&self) -> Option<usize> {
283 self.max_file_size_mb.map(|mb| mb * 1024 * 1024)
284 }
285}
286
287#[derive(Debug, Clone, Copy)]
289pub enum SecurityConfigPreset {
290 Default,
292 JavaScript,
294 Python,
296 HighSecurity,
298 FastCI,
300}
301
302impl SecurityConfigPreset {
303 pub fn to_config(self) -> SecurityAnalysisConfig {
304 match self {
305 Self::Default => SecurityAnalysisConfig::default(),
306 Self::JavaScript => SecurityAnalysisConfig::for_javascript(),
307 Self::Python => SecurityAnalysisConfig::for_python(),
308 Self::HighSecurity => SecurityAnalysisConfig::high_security(),
309 Self::FastCI => SecurityAnalysisConfig::fast_ci(),
310 }
311 }
312}
313
314impl From<SecurityConfigPreset> for SecurityAnalysisConfig {
315 fn from(preset: SecurityConfigPreset) -> Self {
316 preset.to_config()
317 }
318}