go_brrr/security/
crypto.rs

1//! Weak cryptography detection for source code.
2//!
3//! Detects insecure cryptographic patterns including:
4//! - Weak hash functions (MD5, SHA1) used for security purposes
5//! - Weak ciphers (DES, 3DES, RC4, Blowfish)
6//! - Insecure cipher modes (ECB)
7//! - Insufficient key sizes (RSA < 2048 bits)
8//! - Hardcoded encryption keys and IVs
9//! - Predictable random number generation for crypto
10//!
11//! # Context-Aware Analysis
12//!
13//! The detector distinguishes between safe and unsafe uses:
14//! - **SAFE**: MD5/SHA1 for file checksums, cache keys, git operations
15//! - **UNSAFE**: MD5/SHA1 for passwords, signatures, authentication tokens
16//!
17//! # Language Support
18//!
19//! Detects patterns in:
20//! - Python: hashlib, Crypto, cryptography, PyCryptodome
21//! - TypeScript/JavaScript: crypto, crypto-js, node:crypto
22//! - Rust: md5, sha1, des crates, ring, rust-crypto
23//! - Go: crypto/md5, crypto/des, crypto/rc4
24//! - Java: MessageDigest, Cipher
25//! - C/C++: OpenSSL, EVP, MD5(), DES_*
26//!
27//! # Example
28//!
29//! ```ignore
30//! use go_brrr::security::crypto::{WeakCryptoDetector, scan_weak_crypto};
31//!
32//! let result = scan_weak_crypto("./src", None)?;
33//! for finding in result.findings {
34//!     println!("[{}] {} at {}:{}",
35//!         finding.severity,
36//!         finding.issue_type,
37//!         finding.location.file,
38//!         finding.location.line
39//!     );
40//! }
41//! ```
42
43use std::collections::{HashMap, HashSet};
44use std::path::Path;
45
46use once_cell::sync::Lazy;
47use regex::Regex;
48use serde::{Deserialize, Serialize};
49
50use crate::error::{Result, BrrrError};
51
52// =============================================================================
53// Types
54// =============================================================================
55
56/// Type of weak cryptography issue detected.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
58pub enum WeakCryptoIssue {
59    /// Weak hash algorithm (MD5, SHA1 for security purposes)
60    WeakHash,
61    /// Weak cipher algorithm (DES, 3DES, RC4, Blowfish)
62    WeakCipher,
63    /// Insecure cipher mode (ECB)
64    InsecureMode,
65    /// Insufficient key size (RSA < 2048, AES < 128)
66    InsufficientKeySize,
67    /// Hardcoded encryption key
68    HardcodedKey,
69    /// Hardcoded initialization vector (IV should be random)
70    HardcodedIv,
71    /// Predictable random number generator used for crypto
72    PredictableRandom,
73    /// Encryption without authentication (no MAC/HMAC)
74    MissingAuthentication,
75    /// Deprecated cryptographic function
76    DeprecatedFunction,
77}
78
79impl std::fmt::Display for WeakCryptoIssue {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            WeakCryptoIssue::WeakHash => write!(f, "Weak Hash Algorithm"),
83            WeakCryptoIssue::WeakCipher => write!(f, "Weak Cipher Algorithm"),
84            WeakCryptoIssue::InsecureMode => write!(f, "Insecure Cipher Mode"),
85            WeakCryptoIssue::InsufficientKeySize => write!(f, "Insufficient Key Size"),
86            WeakCryptoIssue::HardcodedKey => write!(f, "Hardcoded Encryption Key"),
87            WeakCryptoIssue::HardcodedIv => write!(f, "Hardcoded Initialization Vector"),
88            WeakCryptoIssue::PredictableRandom => write!(f, "Predictable Random for Crypto"),
89            WeakCryptoIssue::MissingAuthentication => write!(f, "Encryption Without Authentication"),
90            WeakCryptoIssue::DeprecatedFunction => write!(f, "Deprecated Cryptographic Function"),
91        }
92    }
93}
94
95/// Specific algorithm that was flagged.
96#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
97pub enum Algorithm {
98    /// MD5 hash (broken, collision attacks)
99    Md5,
100    /// SHA-1 hash (deprecated, collision attacks)
101    Sha1,
102    /// DES cipher (56-bit key, broken)
103    Des,
104    /// Triple DES (deprecated, slow, 112-bit effective)
105    TripleDes,
106    /// RC4 stream cipher (biased keystream, broken)
107    Rc4,
108    /// Blowfish (64-bit block, deprecated)
109    Blowfish,
110    /// ECB mode (reveals patterns in data)
111    EcbMode,
112    /// RSA with insufficient key size
113    RsaWeakKey,
114    /// DSA (deprecated in favor of ECDSA)
115    Dsa,
116    /// Hardcoded key pattern
117    HardcodedKeyPattern,
118    /// Hardcoded IV pattern
119    HardcodedIvPattern,
120    /// Non-cryptographic random
121    InsecureRandom,
122    /// Other weak algorithm
123    Other(String),
124}
125
126impl std::fmt::Display for Algorithm {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        match self {
129            Algorithm::Md5 => write!(f, "MD5"),
130            Algorithm::Sha1 => write!(f, "SHA-1"),
131            Algorithm::Des => write!(f, "DES"),
132            Algorithm::TripleDes => write!(f, "3DES/Triple-DES"),
133            Algorithm::Rc4 => write!(f, "RC4"),
134            Algorithm::Blowfish => write!(f, "Blowfish"),
135            Algorithm::EcbMode => write!(f, "ECB Mode"),
136            Algorithm::RsaWeakKey => write!(f, "RSA (weak key)"),
137            Algorithm::Dsa => write!(f, "DSA"),
138            Algorithm::HardcodedKeyPattern => write!(f, "Hardcoded Key"),
139            Algorithm::HardcodedIvPattern => write!(f, "Hardcoded IV"),
140            Algorithm::InsecureRandom => write!(f, "Insecure Random"),
141            Algorithm::Other(s) => write!(f, "{}", s),
142        }
143    }
144}
145
146/// Severity level for crypto findings.
147#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
148pub enum Severity {
149    /// Informational only (legacy code, may be intentional)
150    Info,
151    /// Low severity (weak but not immediately exploitable)
152    Low,
153    /// Medium severity (deprecated algorithms, should be replaced)
154    Medium,
155    /// High severity (actively dangerous, exploitable)
156    High,
157    /// Critical severity (immediate security risk)
158    Critical,
159}
160
161impl std::fmt::Display for Severity {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        match self {
164            Severity::Info => write!(f, "INFO"),
165            Severity::Low => write!(f, "LOW"),
166            Severity::Medium => write!(f, "MEDIUM"),
167            Severity::High => write!(f, "HIGH"),
168            Severity::Critical => write!(f, "CRITICAL"),
169        }
170    }
171}
172
173/// Confidence level for the detection.
174#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
175pub enum Confidence {
176    /// Low confidence (pattern match only, needs review)
177    Low,
178    /// Medium confidence (likely issue but context unclear)
179    Medium,
180    /// High confidence (clear security issue)
181    High,
182}
183
184impl std::fmt::Display for Confidence {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        match self {
187            Confidence::Low => write!(f, "LOW"),
188            Confidence::Medium => write!(f, "MEDIUM"),
189            Confidence::High => write!(f, "HIGH"),
190        }
191    }
192}
193
194/// Context in which the cryptographic function is used.
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
196pub enum UsageContext {
197    /// Used for password hashing/storage
198    PasswordHashing,
199    /// Used for digital signatures
200    Signature,
201    /// Used for encryption of sensitive data
202    Encryption,
203    /// Used for file checksums (typically safe for MD5)
204    FileChecksum,
205    /// Used for cache key generation (typically safe)
206    CacheKey,
207    /// Used for git operations (typically safe for SHA1)
208    GitOperation,
209    /// Used for HMAC (typically safe)
210    Hmac,
211    /// Unknown context
212    Unknown,
213}
214
215/// Location of a finding in source code.
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct Location {
218    /// File path
219    pub file: String,
220    /// Line number (1-indexed)
221    pub line: usize,
222    /// Column number (1-indexed)
223    pub column: usize,
224    /// End line number (1-indexed)
225    pub end_line: usize,
226    /// End column number (1-indexed)
227    pub end_column: usize,
228}
229
230/// A weak cryptography finding.
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct WeakCryptoFinding {
233    /// Location in source code
234    pub location: Location,
235    /// Type of issue detected
236    pub issue_type: WeakCryptoIssue,
237    /// Specific algorithm flagged
238    pub algorithm: Algorithm,
239    /// Severity level
240    pub severity: Severity,
241    /// Confidence level
242    pub confidence: Confidence,
243    /// Detected usage context
244    pub context: UsageContext,
245    /// Code snippet showing the vulnerable code
246    pub code_snippet: String,
247    /// Human-readable description
248    pub description: String,
249    /// Suggested remediation
250    pub remediation: String,
251    /// Whether this is in a test file
252    pub is_test_file: bool,
253    /// Whether this appears to be safe usage (checksum, cache key)
254    pub likely_safe: bool,
255}
256
257/// Summary of crypto scan results.
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct ScanResult {
260    /// All findings
261    pub findings: Vec<WeakCryptoFinding>,
262    /// Number of files scanned
263    pub files_scanned: usize,
264    /// Counts by issue type
265    pub issue_counts: HashMap<String, usize>,
266    /// Counts by severity
267    pub severity_counts: HashMap<String, usize>,
268    /// Counts by algorithm
269    pub algorithm_counts: HashMap<String, usize>,
270}
271
272// =============================================================================
273// Pattern Definitions
274// =============================================================================
275
276/// Cryptographic pattern with metadata.
277struct CryptoPattern {
278    regex: Regex,
279    issue_type: WeakCryptoIssue,
280    algorithm: Algorithm,
281    base_severity: Severity,
282    confidence: Confidence,
283    description: &'static str,
284    remediation: &'static str,
285    /// Patterns that indicate safe context (checksums, cache)
286    safe_context_patterns: Vec<&'static str>,
287}
288
289/// Context patterns that suggest safe/legitimate usage.
290static SAFE_CONTEXT_PATTERNS: Lazy<Vec<Regex>> = Lazy::new(|| {
291    vec![
292        // Checksum/hash-related variable names and comments
293        Regex::new(r"(?i)checksum").expect("Invalid regex"),
294        Regex::new(r"(?i)file_?hash").expect("Invalid regex"),
295        Regex::new(r"(?i)content_?hash").expect("Invalid regex"),
296        Regex::new(r"(?i)etag").expect("Invalid regex"),
297        Regex::new(r"(?i)cache_?key").expect("Invalid regex"),
298        Regex::new(r"(?i)fingerprint").expect("Invalid regex"),
299        Regex::new(r"(?i)dedup").expect("Invalid regex"),
300        // Git-related
301        Regex::new(r"(?i)git").expect("Invalid regex"),
302        Regex::new(r"(?i)commit").expect("Invalid regex"),
303        Regex::new(r"(?i)blob").expect("Invalid regex"),
304        Regex::new(r"(?i)tree").expect("Invalid regex"),
305        // Legacy compatibility comments
306        Regex::new(r"(?i)legacy").expect("Invalid regex"),
307        Regex::new(r"(?i)backward.?compat").expect("Invalid regex"),
308        Regex::new(r"(?i)deprecated.?but.?required").expect("Invalid regex"),
309        // Verification/comparison (not security)
310        Regex::new(r"(?i)verify_?integrity").expect("Invalid regex"),
311        Regex::new(r"(?i)compare_?hash").expect("Invalid regex"),
312    ]
313});
314
315/// Context patterns that suggest security-sensitive usage.
316static SECURITY_CONTEXT_PATTERNS: Lazy<Vec<Regex>> = Lazy::new(|| {
317    vec![
318        // Password-related
319        Regex::new(r"(?i)password").expect("Invalid regex"),
320        Regex::new(r"(?i)passwd").expect("Invalid regex"),
321        Regex::new(r"(?i)pwd").expect("Invalid regex"),
322        Regex::new(r"(?i)credential").expect("Invalid regex"),
323        // Authentication-related
324        Regex::new(r"(?i)auth").expect("Invalid regex"),
325        Regex::new(r"(?i)token").expect("Invalid regex"),
326        Regex::new(r"(?i)session").expect("Invalid regex"),
327        Regex::new(r"(?i)login").expect("Invalid regex"),
328        // Signature-related
329        Regex::new(r"(?i)sign(ature)?").expect("Invalid regex"),
330        Regex::new(r"(?i)verify").expect("Invalid regex"),
331        Regex::new(r"(?i)certificate").expect("Invalid regex"),
332        // Encryption-related
333        Regex::new(r"(?i)encrypt").expect("Invalid regex"),
334        Regex::new(r"(?i)decrypt").expect("Invalid regex"),
335        Regex::new(r"(?i)secret").expect("Invalid regex"),
336        Regex::new(r"(?i)private").expect("Invalid regex"),
337        Regex::new(r"(?i)sensitive").expect("Invalid regex"),
338    ]
339});
340
341/// Build all crypto patterns for detection.
342static CRYPTO_PATTERNS: Lazy<Vec<CryptoPattern>> = Lazy::new(|| {
343    vec![
344        // =======================================================================
345        // Python Patterns
346        // =======================================================================
347
348        // MD5 in Python
349        CryptoPattern {
350            regex: Regex::new(r"(?i)hashlib\s*\.\s*md5\s*\(").expect("Invalid regex"),
351            issue_type: WeakCryptoIssue::WeakHash,
352            algorithm: Algorithm::Md5,
353            base_severity: Severity::Medium,
354            confidence: Confidence::High,
355            description: "MD5 hash detected - collision attacks are practical",
356            remediation: "Use SHA-256 or SHA-3 for checksums, or bcrypt/argon2 for passwords",
357            safe_context_patterns: vec!["checksum", "etag", "cache", "fingerprint"],
358        },
359        CryptoPattern {
360            regex: Regex::new(r#"(?i)hashlib\s*\.\s*new\s*\(\s*["']md5["']"#).expect("Invalid regex"),
361            issue_type: WeakCryptoIssue::WeakHash,
362            algorithm: Algorithm::Md5,
363            base_severity: Severity::Medium,
364            confidence: Confidence::High,
365            description: "MD5 hash via hashlib.new() - collision attacks are practical",
366            remediation: "Use SHA-256 or SHA-3 for checksums, or bcrypt/argon2 for passwords",
367            safe_context_patterns: vec!["checksum", "etag", "cache"],
368        },
369        // SHA1 in Python
370        CryptoPattern {
371            regex: Regex::new(r"(?i)hashlib\s*\.\s*sha1\s*\(").expect("Invalid regex"),
372            issue_type: WeakCryptoIssue::WeakHash,
373            algorithm: Algorithm::Sha1,
374            base_severity: Severity::Medium,
375            confidence: Confidence::High,
376            description: "SHA-1 hash detected - collision attacks demonstrated (SHAttered)",
377            remediation: "Use SHA-256 or SHA-3 for security purposes",
378            safe_context_patterns: vec!["git", "checksum", "legacy"],
379        },
380        CryptoPattern {
381            regex: Regex::new(r#"(?i)hashlib\s*\.\s*new\s*\(\s*["']sha1["']"#).expect("Invalid regex"),
382            issue_type: WeakCryptoIssue::WeakHash,
383            algorithm: Algorithm::Sha1,
384            base_severity: Severity::Medium,
385            confidence: Confidence::High,
386            description: "SHA-1 hash via hashlib.new()",
387            remediation: "Use SHA-256 or SHA-3 for security purposes",
388            safe_context_patterns: vec!["git", "checksum", "legacy"],
389        },
390        // DES in Python (PyCryptodome) - direct import
391        CryptoPattern {
392            regex: Regex::new(r"(?i)(?:Crypto|Cryptodome)\s*\.\s*Cipher\s*\.\s*DES\b").expect("Invalid regex"),
393            issue_type: WeakCryptoIssue::WeakCipher,
394            algorithm: Algorithm::Des,
395            base_severity: Severity::High,
396            confidence: Confidence::High,
397            description: "DES cipher detected - 56-bit key is trivially brute-forceable",
398            remediation: "Use AES-256 instead",
399            safe_context_patterns: vec![],
400        },
401        // DES in Python (PyCryptodome) - from import style
402        CryptoPattern {
403            regex: Regex::new(r"(?i)from\s+(?:Crypto|Cryptodome)\s*\.\s*Cipher\s+import\s+DES\b").expect("Invalid regex"),
404            issue_type: WeakCryptoIssue::WeakCipher,
405            algorithm: Algorithm::Des,
406            base_severity: Severity::High,
407            confidence: Confidence::High,
408            description: "DES cipher import detected - 56-bit key is trivially brute-forceable",
409            remediation: "Use AES-256 instead",
410            safe_context_patterns: vec![],
411        },
412        // 3DES in Python
413        CryptoPattern {
414            regex: Regex::new(r"(?i)(?:Crypto|Cryptodome)\s*\.\s*Cipher\s*\.\s*(?:DES3|TripleDES)\b").expect("Invalid regex"),
415            issue_type: WeakCryptoIssue::WeakCipher,
416            algorithm: Algorithm::TripleDes,
417            base_severity: Severity::Medium,
418            confidence: Confidence::High,
419            description: "Triple DES detected - deprecated, use AES instead",
420            remediation: "Use AES-256 instead",
421            safe_context_patterns: vec!["legacy"],
422        },
423        // Blowfish in Python
424        CryptoPattern {
425            regex: Regex::new(r"(?i)(?:Crypto|Cryptodome)\s*\.\s*Cipher\s*\.\s*Blowfish\b").expect("Invalid regex"),
426            issue_type: WeakCryptoIssue::WeakCipher,
427            algorithm: Algorithm::Blowfish,
428            base_severity: Severity::Medium,
429            confidence: Confidence::High,
430            description: "Blowfish cipher detected - 64-bit block size leads to birthday attacks",
431            remediation: "Use AES-256 instead (128-bit block size)",
432            safe_context_patterns: vec!["legacy"],
433        },
434        // RC4 in Python
435        CryptoPattern {
436            regex: Regex::new(r"(?i)(?:Crypto|Cryptodome)\s*\.\s*Cipher\s*\.\s*(?:ARC4|RC4)\b").expect("Invalid regex"),
437            issue_type: WeakCryptoIssue::WeakCipher,
438            algorithm: Algorithm::Rc4,
439            base_severity: Severity::High,
440            confidence: Confidence::High,
441            description: "RC4 stream cipher detected - biased keystream, multiple attacks",
442            remediation: "Use AES-GCM or ChaCha20-Poly1305 instead",
443            safe_context_patterns: vec![],
444        },
445        // ECB mode in Python
446        CryptoPattern {
447            regex: Regex::new(r"(?i)MODE_ECB\b").expect("Invalid regex"),
448            issue_type: WeakCryptoIssue::InsecureMode,
449            algorithm: Algorithm::EcbMode,
450            base_severity: Severity::High,
451            confidence: Confidence::High,
452            description: "ECB mode detected - reveals patterns in encrypted data",
453            remediation: "Use CBC with random IV, or preferably GCM/CCM for authenticated encryption",
454            safe_context_patterns: vec![],
455        },
456        // Python cryptography library - weak algorithms
457        CryptoPattern {
458            regex: Regex::new(r"(?i)algorithms\s*\.\s*(?:MD5|SHA1)\b").expect("Invalid regex"),
459            issue_type: WeakCryptoIssue::WeakHash,
460            algorithm: Algorithm::Md5,
461            base_severity: Severity::Medium,
462            confidence: Confidence::High,
463            description: "Weak hash algorithm in cryptography library",
464            remediation: "Use SHA256 or SHA512 for security purposes",
465            safe_context_patterns: vec!["checksum", "legacy"],
466        },
467        // Predictable random in Python for crypto
468        CryptoPattern {
469            regex: Regex::new(r"(?i)random\s*\.\s*(?:random|randint|choice|randrange)\s*\(").expect("Invalid regex"),
470            issue_type: WeakCryptoIssue::PredictableRandom,
471            algorithm: Algorithm::InsecureRandom,
472            base_severity: Severity::High,
473            confidence: Confidence::Medium,
474            description: "Non-cryptographic random used (random module) - predictable",
475            remediation: "Use secrets module or os.urandom() for cryptographic purposes",
476            safe_context_patterns: vec!["test", "mock", "sample", "shuffle"],
477        },
478
479        // =======================================================================
480        // JavaScript/TypeScript Patterns
481        // =======================================================================
482
483        // MD5 in Node.js
484        CryptoPattern {
485            regex: Regex::new(r#"(?i)crypto\s*\.\s*createHash\s*\(\s*["']md5["']"#).expect("Invalid regex"),
486            issue_type: WeakCryptoIssue::WeakHash,
487            algorithm: Algorithm::Md5,
488            base_severity: Severity::Medium,
489            confidence: Confidence::High,
490            description: "MD5 hash in Node.js crypto - collision attacks are practical",
491            remediation: "Use 'sha256' or 'sha512' instead",
492            safe_context_patterns: vec!["checksum", "etag", "cache"],
493        },
494        // SHA1 in Node.js
495        CryptoPattern {
496            regex: Regex::new(r#"(?i)crypto\s*\.\s*createHash\s*\(\s*["']sha1["']"#).expect("Invalid regex"),
497            issue_type: WeakCryptoIssue::WeakHash,
498            algorithm: Algorithm::Sha1,
499            base_severity: Severity::Medium,
500            confidence: Confidence::High,
501            description: "SHA-1 hash in Node.js crypto - collision attacks demonstrated",
502            remediation: "Use 'sha256' or 'sha512' for security purposes",
503            safe_context_patterns: vec!["git", "checksum", "legacy"],
504        },
505        // DES in Node.js
506        CryptoPattern {
507            regex: Regex::new(r#"(?i)crypto\s*\.\s*createCipher(?:iv)?\s*\(\s*["']des(?:-[a-z]+)?["']"#).expect("Invalid regex"),
508            issue_type: WeakCryptoIssue::WeakCipher,
509            algorithm: Algorithm::Des,
510            base_severity: Severity::High,
511            confidence: Confidence::High,
512            description: "DES cipher in Node.js - 56-bit key is trivially brute-forceable",
513            remediation: "Use 'aes-256-gcm' instead",
514            safe_context_patterns: vec![],
515        },
516        // 3DES in Node.js
517        CryptoPattern {
518            regex: Regex::new(r#"(?i)crypto\s*\.\s*createCipher(?:iv)?\s*\(\s*["'](?:des-ede3|des3)(?:-[a-z]+)?["']"#).expect("Invalid regex"),
519            issue_type: WeakCryptoIssue::WeakCipher,
520            algorithm: Algorithm::TripleDes,
521            base_severity: Severity::Medium,
522            confidence: Confidence::High,
523            description: "Triple DES in Node.js - deprecated, slow",
524            remediation: "Use 'aes-256-gcm' instead",
525            safe_context_patterns: vec!["legacy"],
526        },
527        // RC4 in Node.js
528        CryptoPattern {
529            regex: Regex::new(r#"(?i)crypto\s*\.\s*createCipher(?:iv)?\s*\(\s*["']rc4["']"#).expect("Invalid regex"),
530            issue_type: WeakCryptoIssue::WeakCipher,
531            algorithm: Algorithm::Rc4,
532            base_severity: Severity::High,
533            confidence: Confidence::High,
534            description: "RC4 stream cipher in Node.js - broken, biased keystream",
535            remediation: "Use 'aes-256-gcm' or 'chacha20-poly1305' instead",
536            safe_context_patterns: vec![],
537        },
538        // ECB mode in Node.js
539        CryptoPattern {
540            regex: Regex::new(r#"(?i)createCipher(?:iv)?\s*\(\s*["'][a-z0-9-]+-ecb["']"#).expect("Invalid regex"),
541            issue_type: WeakCryptoIssue::InsecureMode,
542            algorithm: Algorithm::EcbMode,
543            base_severity: Severity::High,
544            confidence: Confidence::High,
545            description: "ECB mode in Node.js - reveals patterns in encrypted data",
546            remediation: "Use GCM mode for authenticated encryption",
547            safe_context_patterns: vec![],
548        },
549        // Deprecated crypto.createCipher (no IV)
550        CryptoPattern {
551            regex: Regex::new(r"(?i)crypto\s*\.\s*createCipher\s*\(").expect("Invalid regex"),
552            issue_type: WeakCryptoIssue::DeprecatedFunction,
553            algorithm: Algorithm::Other("createCipher".to_string()),
554            base_severity: Severity::Medium,
555            confidence: Confidence::High,
556            description: "Deprecated createCipher() - uses weak key derivation",
557            remediation: "Use createCipheriv() with a proper key derivation function",
558            safe_context_patterns: vec![],
559        },
560        // crypto-js MD5
561        CryptoPattern {
562            regex: Regex::new(r"(?i)CryptoJS\s*\.\s*MD5\s*\(").expect("Invalid regex"),
563            issue_type: WeakCryptoIssue::WeakHash,
564            algorithm: Algorithm::Md5,
565            base_severity: Severity::Medium,
566            confidence: Confidence::High,
567            description: "MD5 via crypto-js - collision attacks are practical",
568            remediation: "Use CryptoJS.SHA256() for checksums",
569            safe_context_patterns: vec!["checksum", "etag"],
570        },
571        // crypto-js SHA1
572        CryptoPattern {
573            regex: Regex::new(r"(?i)CryptoJS\s*\.\s*SHA1\s*\(").expect("Invalid regex"),
574            issue_type: WeakCryptoIssue::WeakHash,
575            algorithm: Algorithm::Sha1,
576            base_severity: Severity::Medium,
577            confidence: Confidence::High,
578            description: "SHA-1 via crypto-js - collision attacks demonstrated",
579            remediation: "Use CryptoJS.SHA256() for security purposes",
580            safe_context_patterns: vec!["git", "checksum"],
581        },
582        // crypto-js DES
583        CryptoPattern {
584            regex: Regex::new(r"(?i)CryptoJS\s*\.\s*DES\s*\.").expect("Invalid regex"),
585            issue_type: WeakCryptoIssue::WeakCipher,
586            algorithm: Algorithm::Des,
587            base_severity: Severity::High,
588            confidence: Confidence::High,
589            description: "DES via crypto-js - 56-bit key is trivially brute-forceable",
590            remediation: "Use CryptoJS.AES instead",
591            safe_context_patterns: vec![],
592        },
593        // crypto-js 3DES
594        CryptoPattern {
595            regex: Regex::new(r"(?i)CryptoJS\s*\.\s*TripleDES\s*\.").expect("Invalid regex"),
596            issue_type: WeakCryptoIssue::WeakCipher,
597            algorithm: Algorithm::TripleDes,
598            base_severity: Severity::Medium,
599            confidence: Confidence::High,
600            description: "Triple DES via crypto-js - deprecated",
601            remediation: "Use CryptoJS.AES instead",
602            safe_context_patterns: vec!["legacy"],
603        },
604        // crypto-js RC4
605        CryptoPattern {
606            regex: Regex::new(r"(?i)CryptoJS\s*\.\s*(?:RC4|Rabbit)\s*\.").expect("Invalid regex"),
607            issue_type: WeakCryptoIssue::WeakCipher,
608            algorithm: Algorithm::Rc4,
609            base_severity: Severity::High,
610            confidence: Confidence::High,
611            description: "RC4/Rabbit via crypto-js - broken stream cipher",
612            remediation: "Use CryptoJS.AES instead",
613            safe_context_patterns: vec![],
614        },
615        // crypto-js ECB mode
616        CryptoPattern {
617            regex: Regex::new(r"(?i)CryptoJS\s*\.\s*mode\s*\.\s*ECB").expect("Invalid regex"),
618            issue_type: WeakCryptoIssue::InsecureMode,
619            algorithm: Algorithm::EcbMode,
620            base_severity: Severity::High,
621            confidence: Confidence::High,
622            description: "ECB mode via crypto-js - reveals patterns in encrypted data",
623            remediation: "Use CryptoJS.mode.CBC or CryptoJS.mode.CTR with random IV",
624            safe_context_patterns: vec![],
625        },
626        // Math.random() for crypto
627        CryptoPattern {
628            regex: Regex::new(r"(?i)Math\s*\.\s*random\s*\(\s*\)").expect("Invalid regex"),
629            issue_type: WeakCryptoIssue::PredictableRandom,
630            algorithm: Algorithm::InsecureRandom,
631            base_severity: Severity::High,
632            confidence: Confidence::Low, // Low because Math.random is often used legitimately
633            description: "Math.random() is not cryptographically secure",
634            remediation: "Use crypto.randomBytes() or crypto.getRandomValues() for crypto",
635            safe_context_patterns: vec!["ui", "animation", "shuffle", "sample", "test"],
636        },
637
638        // =======================================================================
639        // Go Patterns
640        // =======================================================================
641
642        // MD5 in Go
643        CryptoPattern {
644            regex: Regex::new(r"(?i)md5\s*\.\s*(?:New|Sum)\s*\(").expect("Invalid regex"),
645            issue_type: WeakCryptoIssue::WeakHash,
646            algorithm: Algorithm::Md5,
647            base_severity: Severity::Medium,
648            confidence: Confidence::High,
649            description: "MD5 hash in Go (crypto/md5) - collision attacks are practical",
650            remediation: "Use crypto/sha256 instead",
651            safe_context_patterns: vec!["checksum", "etag", "cache"],
652        },
653        // SHA1 in Go
654        CryptoPattern {
655            regex: Regex::new(r"(?i)sha1\s*\.\s*(?:New|Sum)\s*\(").expect("Invalid regex"),
656            issue_type: WeakCryptoIssue::WeakHash,
657            algorithm: Algorithm::Sha1,
658            base_severity: Severity::Medium,
659            confidence: Confidence::High,
660            description: "SHA-1 hash in Go (crypto/sha1) - collision attacks demonstrated",
661            remediation: "Use crypto/sha256 for security purposes",
662            safe_context_patterns: vec!["git", "checksum", "legacy"],
663        },
664        // DES in Go
665        CryptoPattern {
666            regex: Regex::new(r"(?i)des\s*\.\s*NewCipher\s*\(").expect("Invalid regex"),
667            issue_type: WeakCryptoIssue::WeakCipher,
668            algorithm: Algorithm::Des,
669            base_severity: Severity::High,
670            confidence: Confidence::High,
671            description: "DES cipher in Go (crypto/des) - 56-bit key is trivially brute-forceable",
672            remediation: "Use crypto/aes instead",
673            safe_context_patterns: vec![],
674        },
675        // 3DES in Go
676        CryptoPattern {
677            regex: Regex::new(r"(?i)des\s*\.\s*NewTripleDESCipher\s*\(").expect("Invalid regex"),
678            issue_type: WeakCryptoIssue::WeakCipher,
679            algorithm: Algorithm::TripleDes,
680            base_severity: Severity::Medium,
681            confidence: Confidence::High,
682            description: "Triple DES in Go - deprecated",
683            remediation: "Use crypto/aes instead",
684            safe_context_patterns: vec!["legacy"],
685        },
686        // RC4 in Go
687        CryptoPattern {
688            regex: Regex::new(r"(?i)rc4\s*\.\s*NewCipher\s*\(").expect("Invalid regex"),
689            issue_type: WeakCryptoIssue::WeakCipher,
690            algorithm: Algorithm::Rc4,
691            base_severity: Severity::High,
692            confidence: Confidence::High,
693            description: "RC4 stream cipher in Go (crypto/rc4) - broken, biased keystream",
694            remediation: "Use crypto/aes with GCM mode instead",
695            safe_context_patterns: vec![],
696        },
697        // Go math/rand for crypto
698        CryptoPattern {
699            regex: Regex::new(r#"(?i)"math/rand""#).expect("Invalid regex"),
700            issue_type: WeakCryptoIssue::PredictableRandom,
701            algorithm: Algorithm::InsecureRandom,
702            base_severity: Severity::Medium,
703            confidence: Confidence::Low, // Import doesn't mean crypto usage
704            description: "math/rand import detected - not cryptographically secure",
705            remediation: "Use crypto/rand for cryptographic purposes",
706            safe_context_patterns: vec!["test", "mock", "sample"],
707        },
708
709        // =======================================================================
710        // Rust Patterns
711        // =======================================================================
712
713        // MD5 crate in Rust
714        CryptoPattern {
715            regex: Regex::new(r"(?i)md5\s*::\s*(?:compute|Md5|digest)").expect("Invalid regex"),
716            issue_type: WeakCryptoIssue::WeakHash,
717            algorithm: Algorithm::Md5,
718            base_severity: Severity::Medium,
719            confidence: Confidence::High,
720            description: "MD5 crate usage in Rust - collision attacks are practical",
721            remediation: "Use sha2 crate (Sha256) instead",
722            safe_context_patterns: vec!["checksum", "etag", "cache"],
723        },
724        // SHA1 crate in Rust
725        CryptoPattern {
726            regex: Regex::new(r"(?i)sha1\s*::\s*(?:Sha1|digest)").expect("Invalid regex"),
727            issue_type: WeakCryptoIssue::WeakHash,
728            algorithm: Algorithm::Sha1,
729            base_severity: Severity::Medium,
730            confidence: Confidence::High,
731            description: "SHA-1 crate usage in Rust - collision attacks demonstrated",
732            remediation: "Use sha2 crate (Sha256) for security purposes",
733            safe_context_patterns: vec!["git", "checksum", "legacy"],
734        },
735        // DES in Rust
736        CryptoPattern {
737            regex: Regex::new(r"(?i)des\s*::\s*Des\b").expect("Invalid regex"),
738            issue_type: WeakCryptoIssue::WeakCipher,
739            algorithm: Algorithm::Des,
740            base_severity: Severity::High,
741            confidence: Confidence::High,
742            description: "DES crate usage in Rust - 56-bit key is trivially brute-forceable",
743            remediation: "Use aes crate instead",
744            safe_context_patterns: vec![],
745        },
746        // RC4 in Rust
747        CryptoPattern {
748            regex: Regex::new(r"(?i)rc4\s*::\s*Rc4").expect("Invalid regex"),
749            issue_type: WeakCryptoIssue::WeakCipher,
750            algorithm: Algorithm::Rc4,
751            base_severity: Severity::High,
752            confidence: Confidence::High,
753            description: "RC4 crate usage in Rust - broken, biased keystream",
754            remediation: "Use aes-gcm or chacha20poly1305 crate instead",
755            safe_context_patterns: vec![],
756        },
757        // Blowfish in Rust
758        CryptoPattern {
759            regex: Regex::new(r"(?i)blowfish\s*::\s*Blowfish").expect("Invalid regex"),
760            issue_type: WeakCryptoIssue::WeakCipher,
761            algorithm: Algorithm::Blowfish,
762            base_severity: Severity::Medium,
763            confidence: Confidence::High,
764            description: "Blowfish crate usage in Rust - 64-bit block size leads to birthday attacks",
765            remediation: "Use aes crate instead (128-bit block size)",
766            safe_context_patterns: vec!["legacy"],
767        },
768        // rand crate (non-crypto)
769        CryptoPattern {
770            regex: Regex::new(r"(?i)rand\s*::\s*(?:thread_rng|random)").expect("Invalid regex"),
771            issue_type: WeakCryptoIssue::PredictableRandom,
772            algorithm: Algorithm::InsecureRandom,
773            base_severity: Severity::Medium,
774            confidence: Confidence::Low, // rand is fine for non-crypto
775            description: "rand crate may not be suitable for cryptographic purposes",
776            remediation: "Use rand::rngs::OsRng or the getrandom crate for crypto",
777            safe_context_patterns: vec!["test", "mock", "sample", "shuffle"],
778        },
779
780        // =======================================================================
781        // Java Patterns
782        // =======================================================================
783
784        // MD5 in Java
785        CryptoPattern {
786            regex: Regex::new(r#"(?i)MessageDigest\s*\.\s*getInstance\s*\(\s*["']MD5["']"#).expect("Invalid regex"),
787            issue_type: WeakCryptoIssue::WeakHash,
788            algorithm: Algorithm::Md5,
789            base_severity: Severity::Medium,
790            confidence: Confidence::High,
791            description: "MD5 MessageDigest in Java - collision attacks are practical",
792            remediation: "Use SHA-256: MessageDigest.getInstance(\"SHA-256\")",
793            safe_context_patterns: vec!["checksum", "etag", "cache"],
794        },
795        // SHA1 in Java
796        CryptoPattern {
797            regex: Regex::new(r#"(?i)MessageDigest\s*\.\s*getInstance\s*\(\s*["']SHA-?1["']"#).expect("Invalid regex"),
798            issue_type: WeakCryptoIssue::WeakHash,
799            algorithm: Algorithm::Sha1,
800            base_severity: Severity::Medium,
801            confidence: Confidence::High,
802            description: "SHA-1 MessageDigest in Java - collision attacks demonstrated",
803            remediation: "Use SHA-256: MessageDigest.getInstance(\"SHA-256\")",
804            safe_context_patterns: vec!["git", "checksum", "legacy"],
805        },
806        // DES in Java
807        CryptoPattern {
808            regex: Regex::new(r#"(?i)Cipher\s*\.\s*getInstance\s*\(\s*["']DES[/"']"#).expect("Invalid regex"),
809            issue_type: WeakCryptoIssue::WeakCipher,
810            algorithm: Algorithm::Des,
811            base_severity: Severity::High,
812            confidence: Confidence::High,
813            description: "DES Cipher in Java - 56-bit key is trivially brute-forceable",
814            remediation: "Use AES: Cipher.getInstance(\"AES/GCM/NoPadding\")",
815            safe_context_patterns: vec![],
816        },
817        // 3DES in Java
818        CryptoPattern {
819            regex: Regex::new(r#"(?i)Cipher\s*\.\s*getInstance\s*\(\s*["'](?:DESede|TripleDES)[/"']"#).expect("Invalid regex"),
820            issue_type: WeakCryptoIssue::WeakCipher,
821            algorithm: Algorithm::TripleDes,
822            base_severity: Severity::Medium,
823            confidence: Confidence::High,
824            description: "Triple DES Cipher in Java - deprecated",
825            remediation: "Use AES: Cipher.getInstance(\"AES/GCM/NoPadding\")",
826            safe_context_patterns: vec!["legacy"],
827        },
828        // RC4 in Java
829        CryptoPattern {
830            regex: Regex::new(r#"(?i)Cipher\s*\.\s*getInstance\s*\(\s*["'](?:RC4|ARCFOUR)["']"#).expect("Invalid regex"),
831            issue_type: WeakCryptoIssue::WeakCipher,
832            algorithm: Algorithm::Rc4,
833            base_severity: Severity::High,
834            confidence: Confidence::High,
835            description: "RC4 Cipher in Java - broken, biased keystream",
836            remediation: "Use AES: Cipher.getInstance(\"AES/GCM/NoPadding\")",
837            safe_context_patterns: vec![],
838        },
839        // Blowfish in Java
840        CryptoPattern {
841            regex: Regex::new(r#"(?i)Cipher\s*\.\s*getInstance\s*\(\s*["']Blowfish[/"']"#).expect("Invalid regex"),
842            issue_type: WeakCryptoIssue::WeakCipher,
843            algorithm: Algorithm::Blowfish,
844            base_severity: Severity::Medium,
845            confidence: Confidence::High,
846            description: "Blowfish Cipher in Java - 64-bit block size leads to birthday attacks",
847            remediation: "Use AES: Cipher.getInstance(\"AES/GCM/NoPadding\")",
848            safe_context_patterns: vec!["legacy"],
849        },
850        // ECB mode in Java
851        CryptoPattern {
852            regex: Regex::new(r#"(?i)Cipher\s*\.\s*getInstance\s*\(\s*["'][A-Z]+/ECB/"#).expect("Invalid regex"),
853            issue_type: WeakCryptoIssue::InsecureMode,
854            algorithm: Algorithm::EcbMode,
855            base_severity: Severity::High,
856            confidence: Confidence::High,
857            description: "ECB mode in Java - reveals patterns in encrypted data",
858            remediation: "Use GCM mode: Cipher.getInstance(\"AES/GCM/NoPadding\")",
859            safe_context_patterns: vec![],
860        },
861        // java.util.Random for crypto
862        CryptoPattern {
863            regex: Regex::new(r"(?i)new\s+Random\s*\(").expect("Invalid regex"),
864            issue_type: WeakCryptoIssue::PredictableRandom,
865            algorithm: Algorithm::InsecureRandom,
866            base_severity: Severity::Medium,
867            confidence: Confidence::Low, // Random is often used legitimately
868            description: "java.util.Random is not cryptographically secure",
869            remediation: "Use SecureRandom for cryptographic purposes",
870            safe_context_patterns: vec!["test", "mock", "sample", "shuffle"],
871        },
872        // DSA in Java (deprecated)
873        CryptoPattern {
874            regex: Regex::new(r#"(?i)KeyPairGenerator\s*\.\s*getInstance\s*\(\s*["']DSA["']"#).expect("Invalid regex"),
875            issue_type: WeakCryptoIssue::WeakCipher,
876            algorithm: Algorithm::Dsa,
877            base_severity: Severity::Medium,
878            confidence: Confidence::High,
879            description: "DSA key generation in Java - deprecated in favor of ECDSA",
880            remediation: "Use EC or RSA: KeyPairGenerator.getInstance(\"EC\") or KeyPairGenerator.getInstance(\"RSA\")",
881            safe_context_patterns: vec!["legacy"],
882        },
883
884        // =======================================================================
885        // C/C++ Patterns
886        // =======================================================================
887
888        // MD5 in C (OpenSSL)
889        CryptoPattern {
890            regex: Regex::new(r"(?i)\bMD5(?:_Init|_Update|_Final)?\s*\(").expect("Invalid regex"),
891            issue_type: WeakCryptoIssue::WeakHash,
892            algorithm: Algorithm::Md5,
893            base_severity: Severity::Medium,
894            confidence: Confidence::High,
895            description: "MD5 function in C/OpenSSL - collision attacks are practical",
896            remediation: "Use SHA256_Init/Update/Final or EVP_sha256()",
897            safe_context_patterns: vec!["checksum", "etag", "cache"],
898        },
899        // SHA1 in C (OpenSSL)
900        CryptoPattern {
901            regex: Regex::new(r"(?i)\bSHA1(?:_Init|_Update|_Final)?\s*\(").expect("Invalid regex"),
902            issue_type: WeakCryptoIssue::WeakHash,
903            algorithm: Algorithm::Sha1,
904            base_severity: Severity::Medium,
905            confidence: Confidence::High,
906            description: "SHA-1 function in C/OpenSSL - collision attacks demonstrated",
907            remediation: "Use SHA256_Init/Update/Final or EVP_sha256()",
908            safe_context_patterns: vec!["git", "checksum", "legacy"],
909        },
910        // DES in C (OpenSSL)
911        CryptoPattern {
912            regex: Regex::new(r"(?i)\bDES_(?:set_key|ecb_encrypt|cbc_encrypt|ncbc_encrypt)\s*\(").expect("Invalid regex"),
913            issue_type: WeakCryptoIssue::WeakCipher,
914            algorithm: Algorithm::Des,
915            base_severity: Severity::High,
916            confidence: Confidence::High,
917            description: "DES function in C/OpenSSL - 56-bit key is trivially brute-forceable",
918            remediation: "Use AES with EVP_aes_256_gcm()",
919            safe_context_patterns: vec![],
920        },
921        // RC4 in C (OpenSSL)
922        CryptoPattern {
923            regex: Regex::new(r"(?i)\bRC4(?:_set_key)?\s*\(").expect("Invalid regex"),
924            issue_type: WeakCryptoIssue::WeakCipher,
925            algorithm: Algorithm::Rc4,
926            base_severity: Severity::High,
927            confidence: Confidence::High,
928            description: "RC4 function in C/OpenSSL - broken, biased keystream",
929            remediation: "Use AES-GCM with EVP_aes_256_gcm()",
930            safe_context_patterns: vec![],
931        },
932        // Blowfish in C (OpenSSL)
933        CryptoPattern {
934            regex: Regex::new(r"(?i)\bBF_(?:set_key|ecb_encrypt|cbc_encrypt)\s*\(").expect("Invalid regex"),
935            issue_type: WeakCryptoIssue::WeakCipher,
936            algorithm: Algorithm::Blowfish,
937            base_severity: Severity::Medium,
938            confidence: Confidence::High,
939            description: "Blowfish function in C/OpenSSL - 64-bit block size leads to birthday attacks",
940            remediation: "Use AES with EVP_aes_256_gcm()",
941            safe_context_patterns: vec!["legacy"],
942        },
943        // EVP with weak algorithms
944        CryptoPattern {
945            regex: Regex::new(r"(?i)EVP_(?:md5|sha1)\s*\(").expect("Invalid regex"),
946            issue_type: WeakCryptoIssue::WeakHash,
947            algorithm: Algorithm::Md5,
948            base_severity: Severity::Medium,
949            confidence: Confidence::High,
950            description: "Weak hash via EVP interface - collision attacks possible",
951            remediation: "Use EVP_sha256() or EVP_sha512()",
952            safe_context_patterns: vec!["checksum", "etag", "cache"],
953        },
954        // EVP with DES
955        CryptoPattern {
956            regex: Regex::new(r"(?i)EVP_des_(?:ecb|cbc|cfb|ofb)\s*\(").expect("Invalid regex"),
957            issue_type: WeakCryptoIssue::WeakCipher,
958            algorithm: Algorithm::Des,
959            base_severity: Severity::High,
960            confidence: Confidence::High,
961            description: "DES via EVP interface - 56-bit key is trivially brute-forceable",
962            remediation: "Use EVP_aes_256_gcm()",
963            safe_context_patterns: vec![],
964        },
965        // EVP ECB mode
966        CryptoPattern {
967            regex: Regex::new(r"(?i)EVP_[a-z0-9]+_ecb\s*\(").expect("Invalid regex"),
968            issue_type: WeakCryptoIssue::InsecureMode,
969            algorithm: Algorithm::EcbMode,
970            base_severity: Severity::High,
971            confidence: Confidence::High,
972            description: "ECB mode via EVP interface - reveals patterns in encrypted data",
973            remediation: "Use GCM mode: EVP_aes_256_gcm()",
974            safe_context_patterns: vec![],
975        },
976        // rand() for crypto in C
977        CryptoPattern {
978            regex: Regex::new(r"\brand\s*\(\s*\)").expect("Invalid regex"),
979            issue_type: WeakCryptoIssue::PredictableRandom,
980            algorithm: Algorithm::InsecureRandom,
981            base_severity: Severity::High,
982            confidence: Confidence::Medium,
983            description: "rand() is not cryptographically secure",
984            remediation: "Use RAND_bytes() from OpenSSL or /dev/urandom",
985            safe_context_patterns: vec!["test", "mock", "sample"],
986        },
987
988        // =======================================================================
989        // Hardcoded Key/IV Patterns (Language-agnostic)
990        // =======================================================================
991
992        // Hardcoded encryption key patterns
993        CryptoPattern {
994            regex: Regex::new(r#"(?i)(?:encryption_?key|secret_?key|aes_?key|cipher_?key)\s*[=:]\s*["'][A-Za-z0-9+/=]{16,}["']"#).expect("Invalid regex"),
995            issue_type: WeakCryptoIssue::HardcodedKey,
996            algorithm: Algorithm::HardcodedKeyPattern,
997            base_severity: Severity::Critical,
998            confidence: Confidence::High,
999            description: "Hardcoded encryption key detected - keys should never be in source code",
1000            remediation: "Store keys in environment variables, key management service, or secure vault",
1001            safe_context_patterns: vec!["test", "example", "mock", "placeholder"],
1002        },
1003        // Hardcoded IV patterns
1004        CryptoPattern {
1005            regex: Regex::new(r#"(?i)(?:iv|init(?:ialization)?_?vector|nonce)\s*[=:]\s*["'][A-Za-z0-9+/=]{12,}["']"#).expect("Invalid regex"),
1006            issue_type: WeakCryptoIssue::HardcodedIv,
1007            algorithm: Algorithm::HardcodedIvPattern,
1008            base_severity: Severity::High,
1009            confidence: Confidence::High,
1010            description: "Hardcoded IV/nonce detected - IV should be random for each encryption",
1011            remediation: "Generate random IV using cryptographic random number generator",
1012            safe_context_patterns: vec!["test", "example", "mock"],
1013        },
1014        // Hex-encoded key patterns (32+ hex chars often means 128+ bit key)
1015        CryptoPattern {
1016            regex: Regex::new(r#"(?i)(?:key|secret)\s*[=:]\s*["'][0-9a-fA-F]{32,}["']"#).expect("Invalid regex"),
1017            issue_type: WeakCryptoIssue::HardcodedKey,
1018            algorithm: Algorithm::HardcodedKeyPattern,
1019            base_severity: Severity::Critical,
1020            confidence: Confidence::Medium,
1021            description: "Possible hardcoded key (hex string) - keys should never be in source code",
1022            remediation: "Store keys in environment variables, key management service, or secure vault",
1023            safe_context_patterns: vec!["test", "example", "mock", "checksum"],
1024        },
1025        // Base64-encoded key patterns (common for 128/256-bit keys)
1026        CryptoPattern {
1027            regex: Regex::new(r#"(?i)(?:encryption|cipher|aes|secret).*[=:]\s*["'][A-Za-z0-9+/]{22,}={0,2}["']"#).expect("Invalid regex"),
1028            issue_type: WeakCryptoIssue::HardcodedKey,
1029            algorithm: Algorithm::HardcodedKeyPattern,
1030            base_severity: Severity::High,
1031            confidence: Confidence::Medium,
1032            description: "Possible hardcoded key (base64) - keys should never be in source code",
1033            remediation: "Store keys in environment variables, key management service, or secure vault",
1034            safe_context_patterns: vec!["test", "example", "mock"],
1035        },
1036
1037        // =======================================================================
1038        // RSA Key Size Patterns
1039        // =======================================================================
1040
1041        // RSA with small key in Python
1042        CryptoPattern {
1043            regex: Regex::new(r"(?i)RSA\.generate\s*\(\s*(?:512|768|1024)\s*[,)]").expect("Invalid regex"),
1044            issue_type: WeakCryptoIssue::InsufficientKeySize,
1045            algorithm: Algorithm::RsaWeakKey,
1046            base_severity: Severity::High,
1047            confidence: Confidence::High,
1048            description: "RSA key size < 2048 bits - can be factored with modern hardware",
1049            remediation: "Use at least 2048-bit RSA keys, preferably 4096-bit",
1050            safe_context_patterns: vec!["test"],
1051        },
1052        // RSA with small key in Java
1053        CryptoPattern {
1054            regex: Regex::new(r"(?i)\.initialize\s*\(\s*(?:512|768|1024)\s*[,)]").expect("Invalid regex"),
1055            issue_type: WeakCryptoIssue::InsufficientKeySize,
1056            algorithm: Algorithm::RsaWeakKey,
1057            base_severity: Severity::High,
1058            confidence: Confidence::Medium,
1059            description: "RSA key size < 2048 bits - can be factored with modern hardware",
1060            remediation: "Use at least 2048-bit RSA keys, preferably 4096-bit",
1061            safe_context_patterns: vec!["test"],
1062        },
1063        // Generic key size check for RSA
1064        CryptoPattern {
1065            regex: Regex::new(r"(?i)(?:rsa|key_?size|modulus_?(?:size|length))\s*[=:]\s*(?:512|768|1024)\b").expect("Invalid regex"),
1066            issue_type: WeakCryptoIssue::InsufficientKeySize,
1067            algorithm: Algorithm::RsaWeakKey,
1068            base_severity: Severity::High,
1069            confidence: Confidence::Medium,
1070            description: "RSA key size < 2048 bits specified",
1071            remediation: "Use at least 2048-bit RSA keys, preferably 4096-bit",
1072            safe_context_patterns: vec!["test", "min_", "minimum"],
1073        },
1074    ]
1075});
1076
1077// =============================================================================
1078// Detector Implementation
1079// =============================================================================
1080
1081/// Weak cryptography detector for multiple languages.
1082pub struct WeakCryptoDetector {
1083    /// Whether to skip test files
1084    skip_test_files: bool,
1085    /// Whether to include likely-safe patterns (checksums, cache keys)
1086    include_safe_patterns: bool,
1087}
1088
1089impl Default for WeakCryptoDetector {
1090    fn default() -> Self {
1091        Self::new()
1092    }
1093}
1094
1095impl WeakCryptoDetector {
1096    /// Create a new weak crypto detector with default settings.
1097    pub fn new() -> Self {
1098        Self {
1099            skip_test_files: false,
1100            include_safe_patterns: false, // By default, don't report safe usages
1101        }
1102    }
1103
1104    /// Set whether to skip test files.
1105    #[must_use]
1106    pub fn skip_test_files(mut self, skip: bool) -> Self {
1107        self.skip_test_files = skip;
1108        self
1109    }
1110
1111    /// Set whether to include likely-safe patterns (checksums, cache keys).
1112    #[must_use]
1113    pub fn include_safe_patterns(mut self, include: bool) -> Self {
1114        self.include_safe_patterns = include;
1115        self
1116    }
1117
1118    /// Check if a file is a test file.
1119    fn is_test_file(path: &Path) -> bool {
1120        let path_str = path.to_string_lossy().to_lowercase();
1121
1122        // Check directory names
1123        if path_str.contains("/test/")
1124            || path_str.contains("/tests/")
1125            || path_str.contains("/__tests__/")
1126            || path_str.contains("/spec/")
1127            || path_str.contains("/specs/")
1128            || path_str.contains("/_test/")
1129            || path_str.contains("/test_")
1130            || path_str.contains("/testdata/")
1131            || path_str.contains("/fixtures/")
1132            || path_str.contains("/mocks/")
1133        {
1134            return true;
1135        }
1136
1137        // Check file names
1138        if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
1139            let name_lower = name.to_lowercase();
1140            if name_lower.starts_with("test_")
1141                || name_lower.ends_with("_test.py")
1142                || name_lower.ends_with("_test.go")
1143                || name_lower.ends_with("_test.rs")
1144                || name_lower.ends_with("_test.ts")
1145                || name_lower.ends_with("_test.js")
1146                || name_lower.ends_with(".test.ts")
1147                || name_lower.ends_with(".test.js")
1148                || name_lower.ends_with(".test.tsx")
1149                || name_lower.ends_with(".test.jsx")
1150                || name_lower.ends_with(".spec.ts")
1151                || name_lower.ends_with(".spec.js")
1152                || name_lower.ends_with("_spec.rb")
1153                || name_lower.ends_with("test.java")
1154            {
1155                return true;
1156            }
1157        }
1158
1159        false
1160    }
1161
1162    /// Analyze context around a match to determine if it's security-sensitive.
1163    ///
1164    /// Context priority:
1165    /// 1. CURRENT LINE safe indicators (checksum, cache, git) - if present, line is likely safe
1166    /// 2. CURRENT LINE security indicators (password, auth, token) - if present, line is sensitive
1167    /// 3. SURROUNDING LINES security indicators - might indicate sensitive context
1168    /// 4. Unknown context
1169    fn analyze_context(line: &str, surrounding_lines: &[&str]) -> UsageContext {
1170        let line_lower = line.to_lowercase();
1171        let surrounding = surrounding_lines.join(" ").to_lowercase();
1172
1173        // FIRST: Check CURRENT LINE for safe context indicators
1174        // If the current line mentions checksum/cache/git, it's likely safe usage
1175        if line_lower.contains("checksum")
1176            || line_lower.contains("file_hash")
1177            || line_lower.contains("content_hash")
1178            || line_lower.contains("filehash")
1179        {
1180            return UsageContext::FileChecksum;
1181        }
1182        if line_lower.contains("cache_key") || line_lower.contains("cache") && line_lower.contains("hash") {
1183            return UsageContext::CacheKey;
1184        }
1185        if line_lower.contains("git") && !line_lower.contains("digit") {
1186            return UsageContext::GitOperation;
1187        }
1188        if line_lower.contains("hmac") {
1189            return UsageContext::Hmac;
1190        }
1191
1192        // SECOND: Check CURRENT LINE for security-sensitive context
1193        if line_lower.contains("password") || line_lower.contains("passwd") || line_lower.contains("pwd") {
1194            return UsageContext::PasswordHashing;
1195        }
1196        if line_lower.contains("signature") || (line_lower.contains("sign") && !line_lower.contains("assign")) {
1197            return UsageContext::Signature;
1198        }
1199        if line_lower.contains("encrypt") || line_lower.contains("cipher") {
1200            return UsageContext::Encryption;
1201        }
1202        if line_lower.contains("token") || line_lower.contains("credential") || line_lower.contains("auth") {
1203            return UsageContext::PasswordHashing;
1204        }
1205
1206        // THIRD: Check SURROUNDING LINES for security-sensitive context
1207        // (don't inherit safe context from surrounding lines - only security context)
1208        if surrounding.contains("password") || surrounding.contains("passwd") {
1209            return UsageContext::PasswordHashing;
1210        }
1211        if surrounding.contains("signature") || (surrounding.contains("sign") && !surrounding.contains("assign")) {
1212            return UsageContext::Signature;
1213        }
1214        if surrounding.contains("encrypt") || surrounding.contains("cipher") {
1215            return UsageContext::Encryption;
1216        }
1217
1218        UsageContext::Unknown
1219    }
1220
1221    /// Determine if the usage is likely safe based on context.
1222    fn is_likely_safe(
1223        context: UsageContext,
1224        issue_type: WeakCryptoIssue,
1225        algorithm: &Algorithm,
1226    ) -> bool {
1227        match context {
1228            // File checksums with MD5 are generally acceptable
1229            UsageContext::FileChecksum => matches!(
1230                (issue_type, algorithm),
1231                (WeakCryptoIssue::WeakHash, Algorithm::Md5)
1232                    | (WeakCryptoIssue::WeakHash, Algorithm::Sha1)
1233            ),
1234            // Cache keys with weak hashes are acceptable
1235            UsageContext::CacheKey => matches!(
1236                (issue_type, algorithm),
1237                (WeakCryptoIssue::WeakHash, Algorithm::Md5)
1238                    | (WeakCryptoIssue::WeakHash, Algorithm::Sha1)
1239            ),
1240            // Git operations with SHA1 are acceptable (git's design)
1241            UsageContext::GitOperation => matches!(
1242                (issue_type, algorithm),
1243                (WeakCryptoIssue::WeakHash, Algorithm::Sha1)
1244            ),
1245            // HMAC with weak hash is generally still secure due to HMAC construction
1246            UsageContext::Hmac => matches!(
1247                (issue_type, algorithm),
1248                (WeakCryptoIssue::WeakHash, Algorithm::Md5)
1249                    | (WeakCryptoIssue::WeakHash, Algorithm::Sha1)
1250            ),
1251            // All other contexts are potentially unsafe
1252            _ => false,
1253        }
1254    }
1255
1256    /// Adjust severity based on context.
1257    fn adjust_severity(
1258        base_severity: Severity,
1259        context: UsageContext,
1260        is_test: bool,
1261        likely_safe: bool,
1262    ) -> Severity {
1263        // Test files get reduced severity
1264        if is_test {
1265            return match base_severity {
1266                Severity::Critical => Severity::Low,
1267                Severity::High => Severity::Low,
1268                Severity::Medium => Severity::Info,
1269                _ => Severity::Info,
1270            };
1271        }
1272
1273        // Likely safe usage gets reduced severity
1274        if likely_safe {
1275            return match base_severity {
1276                Severity::Critical | Severity::High => Severity::Info,
1277                _ => Severity::Info,
1278            };
1279        }
1280
1281        // Security-sensitive context might increase severity
1282        match context {
1283            UsageContext::PasswordHashing => match base_severity {
1284                Severity::Medium => Severity::High,
1285                Severity::High => Severity::Critical,
1286                s => s,
1287            },
1288            UsageContext::Signature | UsageContext::Encryption => match base_severity {
1289                Severity::Medium => Severity::High,
1290                s => s,
1291            },
1292            _ => base_severity,
1293        }
1294    }
1295
1296    /// Get remediation advice based on issue type and algorithm.
1297    fn get_remediation(issue_type: WeakCryptoIssue, algorithm: &Algorithm) -> String {
1298        match issue_type {
1299            WeakCryptoIssue::WeakHash => {
1300                match algorithm {
1301                    Algorithm::Md5 => "Replace MD5 with SHA-256 for integrity checks, or bcrypt/argon2 for password hashing".to_string(),
1302                    Algorithm::Sha1 => "Replace SHA-1 with SHA-256 for security purposes".to_string(),
1303                    _ => "Use SHA-256 or SHA-3 family of hash functions".to_string(),
1304                }
1305            }
1306            WeakCryptoIssue::WeakCipher => {
1307                match algorithm {
1308                    Algorithm::Des => "Replace DES with AES-256-GCM for authenticated encryption".to_string(),
1309                    Algorithm::TripleDes => "Replace 3DES with AES-256-GCM for better performance and security".to_string(),
1310                    Algorithm::Rc4 => "Replace RC4 with AES-GCM or ChaCha20-Poly1305".to_string(),
1311                    Algorithm::Blowfish => "Replace Blowfish with AES-256 (128-bit block size)".to_string(),
1312                    _ => "Use AES-256-GCM or ChaCha20-Poly1305 for authenticated encryption".to_string(),
1313                }
1314            }
1315            WeakCryptoIssue::InsecureMode => {
1316                "Use GCM or CCM mode for authenticated encryption, or at minimum CBC with random IV".to_string()
1317            }
1318            WeakCryptoIssue::InsufficientKeySize => {
1319                "Use at least 2048-bit RSA keys (4096-bit recommended), or switch to ECC (P-256 or better)".to_string()
1320            }
1321            WeakCryptoIssue::HardcodedKey => {
1322                "Store encryption keys in:\n  - Environment variables\n  - Key management service (AWS KMS, HashiCorp Vault)\n  - Hardware security module (HSM)\n  Never commit keys to source control".to_string()
1323            }
1324            WeakCryptoIssue::HardcodedIv => {
1325                "Generate a random IV for each encryption operation:\n  - Python: os.urandom(16)\n  - Node.js: crypto.randomBytes(16)\n  - Go: io.ReadFull(rand.Reader, iv)\n  Store IV alongside ciphertext".to_string()
1326            }
1327            WeakCryptoIssue::PredictableRandom => {
1328                "Use cryptographically secure random:\n  - Python: secrets module or os.urandom()\n  - Node.js: crypto.randomBytes()\n  - Go: crypto/rand\n  - Rust: rand::rngs::OsRng or getrandom".to_string()
1329            }
1330            WeakCryptoIssue::MissingAuthentication => {
1331                "Use authenticated encryption (AEAD):\n  - AES-GCM\n  - ChaCha20-Poly1305\n  Or add HMAC for integrity verification".to_string()
1332            }
1333            WeakCryptoIssue::DeprecatedFunction => {
1334                "Update to use the modern, recommended API for cryptographic operations".to_string()
1335            }
1336        }
1337    }
1338
1339    /// Scan a single file for weak cryptography.
1340    pub fn scan_file(&self, file_path: &str) -> Result<Vec<WeakCryptoFinding>> {
1341        let path = Path::new(file_path);
1342        let is_test = Self::is_test_file(path);
1343
1344        // Skip test files if configured
1345        if self.skip_test_files && is_test {
1346            return Ok(vec![]);
1347        }
1348
1349        let source = std::fs::read_to_string(path).map_err(|e| BrrrError::io_with_path(e, path))?;
1350        let lines: Vec<&str> = source.lines().collect();
1351
1352        let mut findings = Vec::new();
1353        let mut seen_locations: HashSet<(usize, usize)> = HashSet::new();
1354
1355        for (line_num, line) in lines.iter().enumerate() {
1356            let line_number = line_num + 1;
1357
1358            // Skip comments (simple heuristic)
1359            let trimmed = line.trim();
1360            if trimmed.starts_with('#')
1361                || trimmed.starts_with("//")
1362                || trimmed.starts_with('*')
1363                || trimmed.starts_with("/*")
1364                || trimmed.starts_with("'''")
1365                || trimmed.starts_with("\"\"\"")
1366            {
1367                continue;
1368            }
1369
1370            // Get surrounding lines for context analysis
1371            let start = line_num.saturating_sub(3);
1372            let end = (line_num + 4).min(lines.len());
1373            let surrounding: Vec<&str> = lines[start..end].to_vec();
1374
1375            // Check all patterns
1376            for pattern in CRYPTO_PATTERNS.iter() {
1377                if let Some(m) = pattern.regex.find(line) {
1378                    // Skip if we've already found something at this location
1379                    let loc_key = (line_number, m.start());
1380                    if seen_locations.contains(&loc_key) {
1381                        continue;
1382                    }
1383                    seen_locations.insert(loc_key);
1384
1385                    // Analyze context
1386                    let context = Self::analyze_context(line, &surrounding);
1387
1388                    // Check if this is a safe context based on pattern's safe_context_patterns
1389                    // BUT: only check the CURRENT line, not surrounding lines that might have different context
1390                    let pattern_safe = pattern
1391                        .safe_context_patterns
1392                        .iter()
1393                        .any(|p| line.to_lowercase().contains(p));
1394
1395                    // Security-sensitive contexts should NEVER be considered safe
1396                    // even if nearby lines mention "checksum" etc.
1397                    let is_security_sensitive = matches!(
1398                        context,
1399                        UsageContext::PasswordHashing
1400                            | UsageContext::Signature
1401                            | UsageContext::Encryption
1402                    );
1403
1404                    // Determine if likely safe - security-sensitive always wins
1405                    let likely_safe = !is_security_sensitive
1406                        && (pattern_safe
1407                            || Self::is_likely_safe(context, pattern.issue_type, &pattern.algorithm));
1408
1409                    // Skip safe patterns if not including them
1410                    if likely_safe && !self.include_safe_patterns {
1411                        continue;
1412                    }
1413
1414                    // Adjust severity
1415                    let severity = Self::adjust_severity(
1416                        pattern.base_severity,
1417                        context,
1418                        is_test,
1419                        likely_safe,
1420                    );
1421
1422                    // Get code snippet
1423                    let snippet_start = line_num.saturating_sub(1);
1424                    let snippet_end = (line_num + 2).min(lines.len());
1425                    let code_snippet = lines[snippet_start..snippet_end].join("\n");
1426
1427                    findings.push(WeakCryptoFinding {
1428                        location: Location {
1429                            file: file_path.to_string(),
1430                            line: line_number,
1431                            column: m.start() + 1,
1432                            end_line: line_number,
1433                            end_column: m.end() + 1,
1434                        },
1435                        issue_type: pattern.issue_type,
1436                        algorithm: pattern.algorithm.clone(),
1437                        severity,
1438                        confidence: pattern.confidence,
1439                        context,
1440                        code_snippet,
1441                        description: pattern.description.to_string(),
1442                        remediation: Self::get_remediation(pattern.issue_type, &pattern.algorithm),
1443                        is_test_file: is_test,
1444                        likely_safe,
1445                    });
1446                }
1447            }
1448        }
1449
1450        Ok(findings)
1451    }
1452
1453    /// Scan a directory for weak cryptography.
1454    pub fn scan_directory(&self, dir_path: &str, language: Option<&str>) -> Result<ScanResult> {
1455        let path = Path::new(dir_path);
1456        if !path.is_dir() {
1457            return Err(BrrrError::InvalidArgument(format!(
1458                "Not a directory: {}",
1459                dir_path
1460            )));
1461        }
1462
1463        let mut findings = Vec::new();
1464        let mut files_scanned = 0;
1465
1466        // Walk directory respecting .gitignore and .brrrignore
1467        let mut builder = ignore::WalkBuilder::new(path);
1468        builder.add_custom_ignore_filename(".brrrignore");
1469        builder.hidden(true);
1470
1471        // Define extensions to scan based on language
1472        let extensions: HashSet<&str> = match language {
1473            Some("python") => ["py"].iter().copied().collect(),
1474            Some("typescript") | Some("javascript") => ["ts", "tsx", "js", "jsx", "mjs", "cjs"]
1475                .iter()
1476                .copied()
1477                .collect(),
1478            Some("go") => ["go"].iter().copied().collect(),
1479            Some("rust") => ["rs"].iter().copied().collect(),
1480            Some("java") => ["java"].iter().copied().collect(),
1481            Some("c") => ["c", "h"].iter().copied().collect(),
1482            Some("cpp") => ["cpp", "cc", "cxx", "hpp", "h"].iter().copied().collect(),
1483            _ => [
1484                "py", "ts", "tsx", "js", "jsx", "mjs", "cjs", "go", "rs", "java", "c", "h", "cpp",
1485                "cc", "cxx", "hpp",
1486            ]
1487            .iter()
1488            .copied()
1489            .collect(),
1490        };
1491
1492        for entry in builder.build().flatten() {
1493            let entry_path = entry.path();
1494            if !entry_path.is_file() {
1495                continue;
1496            }
1497
1498            let ext = entry_path
1499                .extension()
1500                .and_then(|e| e.to_str())
1501                .unwrap_or("");
1502
1503            if !extensions.contains(ext) {
1504                continue;
1505            }
1506
1507            files_scanned += 1;
1508
1509            if let Ok(file_findings) = self.scan_file(entry_path.to_str().unwrap_or("")) {
1510                findings.extend(file_findings);
1511            }
1512        }
1513
1514        // Count by issue type, severity, and algorithm
1515        let mut issue_counts: HashMap<String, usize> = HashMap::new();
1516        let mut severity_counts: HashMap<String, usize> = HashMap::new();
1517        let mut algorithm_counts: HashMap<String, usize> = HashMap::new();
1518
1519        for finding in &findings {
1520            *issue_counts
1521                .entry(finding.issue_type.to_string())
1522                .or_insert(0) += 1;
1523            *severity_counts
1524                .entry(finding.severity.to_string())
1525                .or_insert(0) += 1;
1526            *algorithm_counts
1527                .entry(finding.algorithm.to_string())
1528                .or_insert(0) += 1;
1529        }
1530
1531        Ok(ScanResult {
1532            findings,
1533            files_scanned,
1534            issue_counts,
1535            severity_counts,
1536            algorithm_counts,
1537        })
1538    }
1539}
1540
1541// =============================================================================
1542// Public API Functions
1543// =============================================================================
1544
1545/// Scan a file or directory for weak cryptography.
1546///
1547/// # Arguments
1548///
1549/// * `path` - Path to file or directory to scan
1550/// * `language` - Optional language filter (python, typescript, etc.)
1551///
1552/// # Returns
1553///
1554/// Scan result with all findings
1555pub fn scan_weak_crypto(path: &str, language: Option<&str>) -> Result<ScanResult> {
1556    let detector = WeakCryptoDetector::new();
1557    let path_obj = Path::new(path);
1558
1559    if path_obj.is_file() {
1560        let findings = detector.scan_file(path)?;
1561
1562        let mut issue_counts: HashMap<String, usize> = HashMap::new();
1563        let mut severity_counts: HashMap<String, usize> = HashMap::new();
1564        let mut algorithm_counts: HashMap<String, usize> = HashMap::new();
1565
1566        for finding in &findings {
1567            *issue_counts
1568                .entry(finding.issue_type.to_string())
1569                .or_insert(0) += 1;
1570            *severity_counts
1571                .entry(finding.severity.to_string())
1572                .or_insert(0) += 1;
1573            *algorithm_counts
1574                .entry(finding.algorithm.to_string())
1575                .or_insert(0) += 1;
1576        }
1577
1578        Ok(ScanResult {
1579            findings,
1580            files_scanned: 1,
1581            issue_counts,
1582            severity_counts,
1583            algorithm_counts,
1584        })
1585    } else {
1586        detector.scan_directory(path, language)
1587    }
1588}
1589
1590/// Scan a single file for weak cryptography.
1591pub fn scan_file_weak_crypto(path: &Path, _language: Option<&str>) -> Result<Vec<WeakCryptoFinding>> {
1592    let detector = WeakCryptoDetector::new();
1593    detector.scan_file(path.to_str().unwrap_or(""))
1594}
1595
1596// =============================================================================
1597// Tests
1598// =============================================================================
1599
1600#[cfg(test)]
1601mod tests {
1602    use super::*;
1603    use std::io::Write;
1604
1605    fn create_temp_file(content: &str, extension: &str) -> tempfile::NamedTempFile {
1606        let mut file = tempfile::Builder::new()
1607            .suffix(extension)
1608            .tempfile()
1609            .expect("Failed to create temp file");
1610        file.write_all(content.as_bytes())
1611            .expect("Failed to write temp file");
1612        file
1613    }
1614
1615    // =========================================================================
1616    // Python Tests
1617    // =========================================================================
1618
1619    #[test]
1620    fn test_detect_python_md5() {
1621        let source = r#"
1622import hashlib
1623password_hash = hashlib.md5(password.encode()).hexdigest()
1624        "#;
1625        let file = create_temp_file(source, ".py");
1626        let detector = WeakCryptoDetector::new();
1627        let findings = detector
1628            .scan_file(file.path().to_str().unwrap())
1629            .expect("Scan should succeed");
1630
1631        assert!(!findings.is_empty(), "Should detect MD5 usage");
1632        assert_eq!(findings[0].algorithm, Algorithm::Md5);
1633        assert_eq!(findings[0].issue_type, WeakCryptoIssue::WeakHash);
1634    }
1635
1636    #[test]
1637    fn test_detect_python_md5_for_checksum_safe() {
1638        let source = r#"
1639import hashlib
1640# Calculate file checksum
1641file_checksum = hashlib.md5(file_content).hexdigest()
1642        "#;
1643        let file = create_temp_file(source, ".py");
1644        let detector = WeakCryptoDetector::new();
1645        let findings = detector
1646            .scan_file(file.path().to_str().unwrap())
1647            .expect("Scan should succeed");
1648
1649        // By default, safe patterns are not included
1650        assert!(findings.is_empty(), "Should not flag MD5 for checksums by default");
1651    }
1652
1653    #[test]
1654    fn test_detect_python_md5_for_checksum_include_safe() {
1655        let source = r#"
1656import hashlib
1657# Calculate file checksum
1658file_checksum = hashlib.md5(file_content).hexdigest()
1659        "#;
1660        let file = create_temp_file(source, ".py");
1661        let detector = WeakCryptoDetector::new().include_safe_patterns(true);
1662        let findings = detector
1663            .scan_file(file.path().to_str().unwrap())
1664            .expect("Scan should succeed");
1665
1666        assert!(!findings.is_empty(), "Should detect MD5 when including safe patterns");
1667        assert!(findings[0].likely_safe, "Should mark as likely safe");
1668        assert_eq!(findings[0].severity, Severity::Info, "Severity should be reduced");
1669    }
1670
1671    #[test]
1672    fn test_detect_python_sha1() {
1673        let source = r#"
1674import hashlib
1675token = hashlib.sha1(secret.encode()).hexdigest()
1676        "#;
1677        let file = create_temp_file(source, ".py");
1678        let detector = WeakCryptoDetector::new();
1679        let findings = detector
1680            .scan_file(file.path().to_str().unwrap())
1681            .expect("Scan should succeed");
1682
1683        assert!(!findings.is_empty(), "Should detect SHA-1 usage");
1684        assert_eq!(findings[0].algorithm, Algorithm::Sha1);
1685    }
1686
1687    #[test]
1688    fn test_detect_python_des() {
1689        let source = r#"
1690from Crypto.Cipher import DES
1691cipher = DES.new(key, DES.MODE_ECB)
1692        "#;
1693        let file = create_temp_file(source, ".py");
1694        let detector = WeakCryptoDetector::new();
1695        let findings = detector
1696            .scan_file(file.path().to_str().unwrap())
1697            .expect("Scan should succeed");
1698
1699        let des_finding = findings.iter().find(|f| f.algorithm == Algorithm::Des);
1700        assert!(des_finding.is_some(), "Should detect DES cipher");
1701        assert_eq!(des_finding.unwrap().severity, Severity::High);
1702    }
1703
1704    #[test]
1705    fn test_detect_python_ecb_mode() {
1706        let source = r#"
1707from Crypto.Cipher import AES
1708cipher = AES.new(key, AES.MODE_ECB)
1709        "#;
1710        let file = create_temp_file(source, ".py");
1711        let detector = WeakCryptoDetector::new();
1712        let findings = detector
1713            .scan_file(file.path().to_str().unwrap())
1714            .expect("Scan should succeed");
1715
1716        let ecb_finding = findings.iter().find(|f| f.algorithm == Algorithm::EcbMode);
1717        assert!(ecb_finding.is_some(), "Should detect ECB mode");
1718    }
1719
1720    #[test]
1721    fn test_detect_python_predictable_random() {
1722        let source = r#"
1723import random
1724encryption_key = ''.join(random.choice(chars) for _ in range(32))
1725        "#;
1726        let file = create_temp_file(source, ".py");
1727        let detector = WeakCryptoDetector::new();
1728        let findings = detector
1729            .scan_file(file.path().to_str().unwrap())
1730            .expect("Scan should succeed");
1731
1732        let random_finding = findings.iter().find(|f| f.issue_type == WeakCryptoIssue::PredictableRandom);
1733        assert!(random_finding.is_some(), "Should detect insecure random");
1734    }
1735
1736    // =========================================================================
1737    // JavaScript/TypeScript Tests
1738    // =========================================================================
1739
1740    #[test]
1741    fn test_detect_nodejs_md5() {
1742        let source = r#"
1743const crypto = require('crypto');
1744const hash = crypto.createHash('md5').update(password).digest('hex');
1745        "#;
1746        let file = create_temp_file(source, ".js");
1747        let detector = WeakCryptoDetector::new();
1748        let findings = detector
1749            .scan_file(file.path().to_str().unwrap())
1750            .expect("Scan should succeed");
1751
1752        assert!(!findings.is_empty(), "Should detect MD5 in Node.js");
1753        assert_eq!(findings[0].algorithm, Algorithm::Md5);
1754    }
1755
1756    #[test]
1757    fn test_detect_nodejs_des() {
1758        let source = r#"
1759const crypto = require('crypto');
1760const cipher = crypto.createCipher('des', key);
1761        "#;
1762        let file = create_temp_file(source, ".js");
1763        let detector = WeakCryptoDetector::new();
1764        let findings = detector
1765            .scan_file(file.path().to_str().unwrap())
1766            .expect("Scan should succeed");
1767
1768        // Should find both DES and deprecated createCipher
1769        let des_finding = findings.iter().find(|f| f.algorithm == Algorithm::Des);
1770        assert!(des_finding.is_some(), "Should detect DES in Node.js");
1771    }
1772
1773    #[test]
1774    fn test_detect_nodejs_ecb_mode() {
1775        let source = r#"
1776const cipher = crypto.createCipheriv('aes-256-ecb', key, null);
1777        "#;
1778        let file = create_temp_file(source, ".js");
1779        let detector = WeakCryptoDetector::new();
1780        let findings = detector
1781            .scan_file(file.path().to_str().unwrap())
1782            .expect("Scan should succeed");
1783
1784        let ecb_finding = findings.iter().find(|f| f.algorithm == Algorithm::EcbMode);
1785        assert!(ecb_finding.is_some(), "Should detect ECB mode in Node.js");
1786    }
1787
1788    #[test]
1789    fn test_detect_cryptojs_md5() {
1790        let source = r#"
1791const hash = CryptoJS.MD5(password).toString();
1792        "#;
1793        let file = create_temp_file(source, ".js");
1794        let detector = WeakCryptoDetector::new();
1795        let findings = detector
1796            .scan_file(file.path().to_str().unwrap())
1797            .expect("Scan should succeed");
1798
1799        assert!(!findings.is_empty(), "Should detect CryptoJS MD5");
1800    }
1801
1802    // =========================================================================
1803    // Go Tests
1804    // =========================================================================
1805
1806    #[test]
1807    fn test_detect_go_md5() {
1808        let source = r#"
1809package main
1810
1811import "crypto/md5"
1812
1813func hash(data []byte) []byte {
1814    h := md5.New()
1815    h.Write(data)
1816    return h.Sum(nil)
1817}
1818        "#;
1819        let file = create_temp_file(source, ".go");
1820        let detector = WeakCryptoDetector::new();
1821        let findings = detector
1822            .scan_file(file.path().to_str().unwrap())
1823            .expect("Scan should succeed");
1824
1825        assert!(!findings.is_empty(), "Should detect MD5 in Go");
1826        assert_eq!(findings[0].algorithm, Algorithm::Md5);
1827    }
1828
1829    #[test]
1830    fn test_detect_go_des() {
1831        let source = r#"
1832package main
1833
1834import "crypto/des"
1835
1836func encrypt(key, data []byte) {
1837    block, _ := des.NewCipher(key)
1838}
1839        "#;
1840        let file = create_temp_file(source, ".go");
1841        let detector = WeakCryptoDetector::new();
1842        let findings = detector
1843            .scan_file(file.path().to_str().unwrap())
1844            .expect("Scan should succeed");
1845
1846        let des_finding = findings.iter().find(|f| f.algorithm == Algorithm::Des);
1847        assert!(des_finding.is_some(), "Should detect DES in Go");
1848    }
1849
1850    // =========================================================================
1851    // Rust Tests
1852    // =========================================================================
1853
1854    #[test]
1855    fn test_detect_rust_md5() {
1856        let source = r#"
1857use md5::{Md5, Digest};
1858
1859fn hash_password(password: &str) -> String {
1860    let result = md5::compute(password.as_bytes());
1861    format!("{:x}", result)
1862}
1863        "#;
1864        let file = create_temp_file(source, ".rs");
1865        let detector = WeakCryptoDetector::new();
1866        let findings = detector
1867            .scan_file(file.path().to_str().unwrap())
1868            .expect("Scan should succeed");
1869
1870        assert!(!findings.is_empty(), "Should detect MD5 in Rust");
1871    }
1872
1873    // =========================================================================
1874    // Java Tests
1875    // =========================================================================
1876
1877    #[test]
1878    fn test_detect_java_md5() {
1879        let source = r#"
1880import java.security.MessageDigest;
1881
1882public class HashUtil {
1883    public static byte[] hash(String data) throws Exception {
1884        MessageDigest md = MessageDigest.getInstance("MD5");
1885        return md.digest(data.getBytes());
1886    }
1887}
1888        "#;
1889        let file = create_temp_file(source, ".java");
1890        let detector = WeakCryptoDetector::new();
1891        let findings = detector
1892            .scan_file(file.path().to_str().unwrap())
1893            .expect("Scan should succeed");
1894
1895        assert!(!findings.is_empty(), "Should detect MD5 in Java");
1896        assert_eq!(findings[0].algorithm, Algorithm::Md5);
1897    }
1898
1899    #[test]
1900    fn test_detect_java_des() {
1901        let source = r#"
1902import javax.crypto.Cipher;
1903
1904public class CryptoUtil {
1905    public static byte[] encrypt(byte[] data, byte[] key) throws Exception {
1906        Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
1907        return cipher.doFinal(data);
1908    }
1909}
1910        "#;
1911        let file = create_temp_file(source, ".java");
1912        let detector = WeakCryptoDetector::new();
1913        let findings = detector
1914            .scan_file(file.path().to_str().unwrap())
1915            .expect("Scan should succeed");
1916
1917        // Should detect both DES and ECB
1918        let des_finding = findings.iter().find(|f| f.algorithm == Algorithm::Des);
1919        assert!(des_finding.is_some(), "Should detect DES in Java");
1920    }
1921
1922    // =========================================================================
1923    // C/C++ Tests
1924    // =========================================================================
1925
1926    #[test]
1927    fn test_detect_c_md5() {
1928        let source = r#"
1929#include <openssl/md5.h>
1930
1931void hash_password(const char* password, unsigned char* digest) {
1932    MD5((unsigned char*)password, strlen(password), digest);
1933}
1934        "#;
1935        let file = create_temp_file(source, ".c");
1936        let detector = WeakCryptoDetector::new();
1937        let findings = detector
1938            .scan_file(file.path().to_str().unwrap())
1939            .expect("Scan should succeed");
1940
1941        assert!(!findings.is_empty(), "Should detect MD5 in C");
1942        assert_eq!(findings[0].algorithm, Algorithm::Md5);
1943    }
1944
1945    #[test]
1946    fn test_detect_c_des() {
1947        let source = r#"
1948#include <openssl/des.h>
1949
1950void encrypt(DES_cblock* key, unsigned char* data) {
1951    DES_key_schedule schedule;
1952    DES_set_key(key, &schedule);
1953    DES_ecb_encrypt((DES_cblock*)data, (DES_cblock*)data, &schedule, DES_ENCRYPT);
1954}
1955        "#;
1956        let file = create_temp_file(source, ".c");
1957        let detector = WeakCryptoDetector::new();
1958        let findings = detector
1959            .scan_file(file.path().to_str().unwrap())
1960            .expect("Scan should succeed");
1961
1962        let des_finding = findings.iter().find(|f| f.algorithm == Algorithm::Des);
1963        assert!(des_finding.is_some(), "Should detect DES in C");
1964    }
1965
1966    // =========================================================================
1967    // Hardcoded Key/IV Tests
1968    // =========================================================================
1969
1970    #[test]
1971    fn test_detect_hardcoded_key() {
1972        let source = r#"
1973const encryption_key = "0123456789abcdef0123456789abcdef";
1974const cipher = crypto.createCipheriv('aes-256-cbc', encryption_key, iv);
1975        "#;
1976        let file = create_temp_file(source, ".js");
1977        let detector = WeakCryptoDetector::new();
1978        let findings = detector
1979            .scan_file(file.path().to_str().unwrap())
1980            .expect("Scan should succeed");
1981
1982        let key_finding = findings.iter().find(|f| f.issue_type == WeakCryptoIssue::HardcodedKey);
1983        assert!(key_finding.is_some(), "Should detect hardcoded key");
1984        assert_eq!(key_finding.unwrap().severity, Severity::Critical);
1985    }
1986
1987    #[test]
1988    fn test_detect_hardcoded_iv() {
1989        let source = r#"
1990iv = "1234567890123456"
1991cipher = AES.new(key, AES.MODE_CBC, iv)
1992        "#;
1993        let file = create_temp_file(source, ".py");
1994        let detector = WeakCryptoDetector::new();
1995        let findings = detector
1996            .scan_file(file.path().to_str().unwrap())
1997            .expect("Scan should succeed");
1998
1999        let iv_finding = findings.iter().find(|f| f.issue_type == WeakCryptoIssue::HardcodedIv);
2000        assert!(iv_finding.is_some(), "Should detect hardcoded IV");
2001    }
2002
2003    // =========================================================================
2004    // RSA Key Size Tests
2005    // =========================================================================
2006
2007    #[test]
2008    fn test_detect_weak_rsa_python() {
2009        let source = r#"
2010from Crypto.PublicKey import RSA
2011key = RSA.generate(1024)
2012        "#;
2013        let file = create_temp_file(source, ".py");
2014        let detector = WeakCryptoDetector::new();
2015        let findings = detector
2016            .scan_file(file.path().to_str().unwrap())
2017            .expect("Scan should succeed");
2018
2019        let rsa_finding = findings.iter().find(|f| f.issue_type == WeakCryptoIssue::InsufficientKeySize);
2020        assert!(rsa_finding.is_some(), "Should detect weak RSA key size");
2021    }
2022
2023    // =========================================================================
2024    // Context Tests
2025    // =========================================================================
2026
2027    #[test]
2028    fn test_password_context_increases_severity() {
2029        let source = r#"
2030import hashlib
2031# Hash password for storage
2032password_hash = hashlib.sha1(password.encode()).hexdigest()
2033        "#;
2034        let file = create_temp_file(source, ".py");
2035        let detector = WeakCryptoDetector::new();
2036        let findings = detector
2037            .scan_file(file.path().to_str().unwrap())
2038            .expect("Scan should succeed");
2039
2040        assert!(!findings.is_empty(), "Should detect SHA-1 usage");
2041        // Password context should increase severity from Medium to High
2042        assert!(
2043            findings[0].severity >= Severity::High,
2044            "Severity should be elevated for password context"
2045        );
2046    }
2047
2048    #[test]
2049    fn test_test_file_reduces_severity() {
2050        let source = r#"
2051import hashlib
2052hash = hashlib.md5(data).hexdigest()
2053        "#;
2054        let mut file = tempfile::Builder::new()
2055            .suffix("_test.py")
2056            .tempfile()
2057            .expect("Failed to create temp file");
2058        file.write_all(source.as_bytes())
2059            .expect("Failed to write temp file");
2060
2061        let detector = WeakCryptoDetector::new();
2062        let findings = detector
2063            .scan_file(file.path().to_str().unwrap())
2064            .expect("Scan should succeed");
2065
2066        assert!(!findings.is_empty(), "Should detect MD5 in test file");
2067        assert!(findings[0].is_test_file, "Should mark as test file");
2068        assert!(
2069            findings[0].severity <= Severity::Low,
2070            "Severity should be reduced for test files"
2071        );
2072    }
2073
2074    // =========================================================================
2075    // Display Tests
2076    // =========================================================================
2077
2078    #[test]
2079    fn test_issue_type_display() {
2080        assert_eq!(WeakCryptoIssue::WeakHash.to_string(), "Weak Hash Algorithm");
2081        assert_eq!(WeakCryptoIssue::WeakCipher.to_string(), "Weak Cipher Algorithm");
2082        assert_eq!(WeakCryptoIssue::InsecureMode.to_string(), "Insecure Cipher Mode");
2083        assert_eq!(WeakCryptoIssue::HardcodedKey.to_string(), "Hardcoded Encryption Key");
2084    }
2085
2086    #[test]
2087    fn test_algorithm_display() {
2088        assert_eq!(Algorithm::Md5.to_string(), "MD5");
2089        assert_eq!(Algorithm::Sha1.to_string(), "SHA-1");
2090        assert_eq!(Algorithm::Des.to_string(), "DES");
2091        assert_eq!(Algorithm::EcbMode.to_string(), "ECB Mode");
2092    }
2093
2094    #[test]
2095    fn test_severity_display() {
2096        assert_eq!(Severity::Critical.to_string(), "CRITICAL");
2097        assert_eq!(Severity::High.to_string(), "HIGH");
2098        assert_eq!(Severity::Medium.to_string(), "MEDIUM");
2099        assert_eq!(Severity::Low.to_string(), "LOW");
2100        assert_eq!(Severity::Info.to_string(), "INFO");
2101    }
2102
2103    // =========================================================================
2104    // Scan Result Tests
2105    // =========================================================================
2106
2107    #[test]
2108    fn test_scan_result_counts() {
2109        let result = ScanResult {
2110            findings: vec![],
2111            files_scanned: 10,
2112            issue_counts: [("Weak Hash Algorithm".to_string(), 5)]
2113                .into_iter()
2114                .collect(),
2115            severity_counts: [("HIGH".to_string(), 3), ("MEDIUM".to_string(), 2)]
2116                .into_iter()
2117                .collect(),
2118            algorithm_counts: [("MD5".to_string(), 3), ("SHA-1".to_string(), 2)]
2119                .into_iter()
2120                .collect(),
2121        };
2122
2123        assert_eq!(result.files_scanned, 10);
2124        assert_eq!(result.issue_counts.get("Weak Hash Algorithm"), Some(&5));
2125        assert_eq!(result.severity_counts.get("HIGH"), Some(&3));
2126        assert_eq!(result.algorithm_counts.get("MD5"), Some(&3));
2127    }
2128}