redgun-security 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,105 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { getFindings, getSeverityCounts } from '../findings.js';
4
+ import { calculateScore, getGrade, getGradeColor } from '../score.js';
5
+
6
+ export function exportHtml(outputDir = './scans') {
7
+ if (!existsSync(outputDir)) {
8
+ mkdirSync(outputDir, { recursive: true });
9
+ }
10
+
11
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
12
+ const filepath = join(outputDir, `redgun-${timestamp}.html`);
13
+
14
+ const score = calculateScore();
15
+ const grade = getGrade(score);
16
+ const gradeColor = getGradeColor(grade);
17
+ const counts = getSeverityCounts();
18
+ const findings = getFindings();
19
+
20
+ const grouped = {};
21
+ for (const f of findings) {
22
+ if (!grouped[f.severity]) grouped[f.severity] = [];
23
+ grouped[f.severity].push(f);
24
+ }
25
+
26
+ const severityOrder = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO'];
27
+ const severityColors = {
28
+ CRITICAL: '#f44336',
29
+ HIGH: '#ff5722',
30
+ MEDIUM: '#ff9800',
31
+ LOW: '#2196f3',
32
+ INFO: '#9e9e9e',
33
+ };
34
+
35
+ let findingsHtml = '';
36
+ for (const sev of severityOrder) {
37
+ if (!grouped[sev] || grouped[sev].length === 0) continue;
38
+ findingsHtml += `<h3 style="color:${severityColors[sev]}">${sev} (${grouped[sev].length})</h3>`;
39
+ for (const f of grouped[sev]) {
40
+ findingsHtml += `
41
+ <div class="finding" style="border-left:4px solid ${severityColors[sev]}">
42
+ <strong>[${f.module}]</strong> ${escapeHtml(f.title)}
43
+ ${f.details ? `<p class="details">${escapeHtml(f.details)}</p>` : ''}
44
+ ${f.fix ? `<p class="fix"><strong>Fix:</strong> ${escapeHtml(f.fix)}</p>` : ''}
45
+ </div>`;
46
+ }
47
+ }
48
+
49
+ const html = `<!DOCTYPE html>
50
+ <html lang="en">
51
+ <head>
52
+ <meta charset="UTF-8">
53
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
54
+ <title>RedGun Security Report</title>
55
+ <style>
56
+ * { margin: 0; padding: 0; box-sizing: border-box; }
57
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #0d1117; color: #c9d1d9; padding: 2rem; }
58
+ .container { max-width: 900px; margin: 0 auto; }
59
+ h1 { color: #ff4444; font-size: 2rem; margin-bottom: 0.5rem; }
60
+ h2 { color: #58a6ff; margin: 1.5rem 0 1rem; }
61
+ h3 { margin: 1.5rem 0 0.5rem; }
62
+ .score-card { background: #161b22; border-radius: 12px; padding: 2rem; margin: 1.5rem 0; text-align: center; }
63
+ .score { font-size: 4rem; font-weight: bold; color: ${gradeColor}; }
64
+ .grade { font-size: 1.5rem; color: ${gradeColor}; }
65
+ .counts { display: flex; justify-content: center; gap: 1.5rem; margin-top: 1rem; flex-wrap: wrap; }
66
+ .count-item { text-align: center; }
67
+ .count-num { font-size: 1.5rem; font-weight: bold; }
68
+ .finding { background: #161b22; border-radius: 8px; padding: 1rem; margin: 0.5rem 0; }
69
+ .details { color: #8b949e; margin-top: 0.5rem; font-size: 0.9rem; }
70
+ .fix { color: #3fb950; margin-top: 0.5rem; font-size: 0.9rem; }
71
+ .footer { text-align: center; margin-top: 3rem; color: #484f58; }
72
+ </style>
73
+ </head>
74
+ <body>
75
+ <div class="container">
76
+ <h1>RedGun Security Report</h1>
77
+ <p style="color:#8b949e">Generated: ${new Date().toLocaleString()}</p>
78
+ <div class="score-card">
79
+ <div class="score">${score}/100</div>
80
+ <div class="grade">Grade: ${grade}</div>
81
+ <div class="counts">
82
+ <div class="count-item"><div class="count-num" style="color:#f44336">${counts.critical}</div>Critical</div>
83
+ <div class="count-item"><div class="count-num" style="color:#ff5722">${counts.high}</div>High</div>
84
+ <div class="count-item"><div class="count-num" style="color:#ff9800">${counts.medium}</div>Medium</div>
85
+ <div class="count-item"><div class="count-num" style="color:#2196f3">${counts.low}</div>Low</div>
86
+ <div class="count-item"><div class="count-num" style="color:#9e9e9e">${counts.info}</div>Info</div>
87
+ </div>
88
+ </div>
89
+ <h2>Findings (${findings.length} total)</h2>
90
+ ${findingsHtml}
91
+ <div class="footer">
92
+ <p>RedGun Security Scanner v1.0.0 | HackTricks Enhanced</p>
93
+ </div>
94
+ </div>
95
+ </body>
96
+ </html>`;
97
+
98
+ writeFileSync(filepath, html);
99
+ return filepath;
100
+ }
101
+
102
+ function escapeHtml(str) {
103
+ if (!str) return '';
104
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
105
+ }
@@ -0,0 +1,66 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { getFindings, getSeverityCounts } from '../findings.js';
4
+ import { calculateScore, getGrade } from '../score.js';
5
+
6
+ export function exportJson(outputDir = './scans') {
7
+ if (!existsSync(outputDir)) {
8
+ mkdirSync(outputDir, { recursive: true });
9
+ }
10
+
11
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
12
+ const filename = `redgun-${timestamp}.json`;
13
+ const filepath = join(outputDir, filename);
14
+
15
+ const report = {
16
+ tool: 'RedGun Security Scanner',
17
+ version: '1.0.0',
18
+ timestamp: new Date().toISOString(),
19
+ score: calculateScore(),
20
+ grade: getGrade(calculateScore()),
21
+ summary: getSeverityCounts(),
22
+ totalFindings: getFindings().length,
23
+ findings: getFindings(),
24
+ };
25
+
26
+ writeFileSync(filepath, JSON.stringify(report, null, 2));
27
+ return filepath;
28
+ }
29
+
30
+ export function exportSarif(outputDir = './scans') {
31
+ if (!existsSync(outputDir)) {
32
+ mkdirSync(outputDir, { recursive: true });
33
+ }
34
+
35
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
36
+ const filepath = join(outputDir, `redgun-${timestamp}.sarif`);
37
+
38
+ const sarif = {
39
+ $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
40
+ version: '2.1.0',
41
+ runs: [{
42
+ tool: {
43
+ driver: {
44
+ name: 'RedGun',
45
+ version: '1.0.0',
46
+ informationUri: 'https://github.com/aloc999/redgun',
47
+ rules: [],
48
+ },
49
+ },
50
+ results: getFindings().map((f, i) => ({
51
+ ruleId: `REDGUN-${String(i + 1).padStart(3, '0')}`,
52
+ level: f.severity === 'CRITICAL' || f.severity === 'HIGH' ? 'error' : f.severity === 'MEDIUM' ? 'warning' : 'note',
53
+ message: { text: `[${f.module}] ${f.title}: ${f.details || ''}` },
54
+ locations: f.file ? [{
55
+ physicalLocation: {
56
+ artifactLocation: { uri: f.file },
57
+ region: f.line ? { startLine: f.line } : undefined,
58
+ },
59
+ }] : [],
60
+ })),
61
+ }],
62
+ };
63
+
64
+ writeFileSync(filepath, JSON.stringify(sarif, null, 2));
65
+ return filepath;
66
+ }
@@ -0,0 +1,41 @@
1
+ import { getFindings } from './findings.js';
2
+
3
+ const SEVERITY_WEIGHTS = {
4
+ CRITICAL: -15,
5
+ HIGH: -8,
6
+ MEDIUM: -3,
7
+ LOW: -1,
8
+ INFO: 0,
9
+ };
10
+
11
+ export function calculateScore() {
12
+ const findings = getFindings();
13
+ let score = 100;
14
+
15
+ for (const finding of findings) {
16
+ score += SEVERITY_WEIGHTS[finding.severity] || 0;
17
+ }
18
+
19
+ return Math.max(0, Math.min(100, score));
20
+ }
21
+
22
+ export function getGrade(score) {
23
+ if (score >= 90) return 'A';
24
+ if (score >= 80) return 'B';
25
+ if (score >= 70) return 'C';
26
+ if (score >= 60) return 'D';
27
+ if (score >= 50) return 'E';
28
+ return 'F';
29
+ }
30
+
31
+ export function getGradeColor(grade) {
32
+ const colors = {
33
+ A: '#4caf50',
34
+ B: '#8bc34a',
35
+ C: '#ffeb3b',
36
+ D: '#ff9800',
37
+ E: '#ff5722',
38
+ F: '#f44336',
39
+ };
40
+ return colors[grade] || '#f44336';
41
+ }
@@ -0,0 +1,121 @@
1
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+
5
+ const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.rb', '.php'];
6
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '.next'];
7
+
8
+ export async function auditAuth(projectPath, spinner) {
9
+ spinner.text = 'Checking auth & middleware configuration...';
10
+ const files = getFiles(projectPath);
11
+ let hasRateLimit = false;
12
+ let hasCors = false;
13
+ let hasCsrf = false;
14
+ let hasHelmet = false;
15
+
16
+ const packagePath = join(projectPath, 'package.json');
17
+ if (existsSync(packagePath)) {
18
+ try {
19
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
20
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
21
+ hasRateLimit = !!allDeps['express-rate-limit'] || !!allDeps['rate-limiter-flexible'];
22
+ hasCors = !!allDeps['cors'];
23
+ hasCsrf = !!allDeps['csurf'] || !!allDeps['csrf'] || !!allDeps['lusca'];
24
+ hasHelmet = !!allDeps['helmet'];
25
+ } catch {}
26
+ }
27
+
28
+ if (!hasRateLimit) {
29
+ addFinding(
30
+ 'HIGH',
31
+ 'Auth & Middleware',
32
+ 'No rate limiting detected',
33
+ 'No rate-limit package found in dependencies',
34
+ 'Install express-rate-limit or rate-limiter-flexible to prevent brute-force attacks'
35
+ );
36
+ }
37
+
38
+ if (!hasCsrf) {
39
+ addFinding(
40
+ 'MEDIUM',
41
+ 'Auth & Middleware',
42
+ 'No CSRF protection detected',
43
+ 'No CSRF package found in dependencies',
44
+ 'Implement CSRF tokens using csurf or lusca middleware'
45
+ );
46
+ }
47
+
48
+ if (!hasHelmet) {
49
+ addFinding(
50
+ 'MEDIUM',
51
+ 'Auth & Middleware',
52
+ 'No helmet (security headers) detected',
53
+ 'Helmet package not found in dependencies',
54
+ 'Install helmet to set secure HTTP headers automatically'
55
+ );
56
+ }
57
+
58
+ for (const file of files) {
59
+ try {
60
+ const content = readFileSync(file, 'utf-8');
61
+ const relativePath = file.replace(projectPath, '.');
62
+
63
+ if (/cors\(\s*\{\s*origin\s*:\s*['"`]\*['"`]/gi.test(content) || /cors\(\s*\)/g.test(content)) {
64
+ addFinding(
65
+ 'MEDIUM',
66
+ 'Auth & Middleware',
67
+ 'CORS wildcard origin detected',
68
+ `File: ${relativePath}`,
69
+ 'Restrict CORS origin to specific trusted domains instead of using wildcard *'
70
+ );
71
+ }
72
+
73
+ if (/session\s*\(\s*\{[^}]*secret\s*:\s*['"][^'"]{1,8}['"]/gi.test(content)) {
74
+ addFinding(
75
+ 'HIGH',
76
+ 'Auth & Middleware',
77
+ 'Weak session secret',
78
+ `File: ${relativePath}`,
79
+ 'Use a strong, random session secret (at least 32 characters) from environment variables'
80
+ );
81
+ }
82
+
83
+ if (/(?:expiresIn|exp)\s*:\s*['"]?\d{5,}['"]?/gi.test(content)) {
84
+ addFinding(
85
+ 'LOW',
86
+ 'Auth & Middleware',
87
+ 'Long JWT/session expiration',
88
+ `File: ${relativePath}`,
89
+ 'Consider shorter token expiration with refresh token rotation'
90
+ );
91
+ }
92
+
93
+ const hardcodedPwdPattern = /(?:password|passwd)\s*(?:===?|!==?)\s*['"][^'"]+['"]/gi;
94
+ if (hardcodedPwdPattern.test(content)) {
95
+ addFinding(
96
+ 'CRITICAL',
97
+ 'Auth & Middleware',
98
+ 'Hardcoded password comparison',
99
+ `File: ${relativePath}`,
100
+ 'Never hardcode passwords. Use bcrypt/argon2 hash comparison against stored hashes.'
101
+ );
102
+ }
103
+ } catch {}
104
+ }
105
+ }
106
+
107
+ function getFiles(dir, files = []) {
108
+ try {
109
+ const entries = readdirSync(dir);
110
+ for (const entry of entries) {
111
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
112
+ const fullPath = join(dir, entry);
113
+ try {
114
+ const stat = statSync(fullPath);
115
+ if (stat.isDirectory()) getFiles(fullPath, files);
116
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
117
+ } catch {}
118
+ }
119
+ } catch {}
120
+ return files;
121
+ }
@@ -0,0 +1,94 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+ import { XSS_PATTERNS, SQLI_PATTERNS } from '../utils/patterns.js';
5
+
6
+ const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.vue', '.svelte', '.php', '.py', '.rb'];
7
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', '__pycache__', 'vendor', 'coverage'];
8
+
9
+ export async function auditCodeVulnerabilities(projectPath, spinner) {
10
+ spinner.text = 'Scanning for code vulnerabilities (SQLi, XSS, eval)...';
11
+ const files = getFiles(projectPath);
12
+
13
+ for (const file of files) {
14
+ try {
15
+ const content = readFileSync(file, 'utf-8');
16
+ const relativePath = file.replace(projectPath, '.');
17
+ const lines = content.split('\n');
18
+
19
+ for (const pattern of SQLI_PATTERNS) {
20
+ const regex = new RegExp(pattern.source, pattern.flags);
21
+ let match;
22
+ while ((match = regex.exec(content)) !== null) {
23
+ const lineNum = content.substring(0, match.index).split('\n').length;
24
+ addFinding(
25
+ 'CRITICAL',
26
+ 'Code Vulnerabilities',
27
+ 'SQL Injection - user input in query',
28
+ `File: ${relativePath}:${lineNum}\nCode: ${lines[lineNum - 1]?.trim().substring(0, 100)}`,
29
+ 'Use parameterized queries or prepared statements. Never concatenate user input into SQL.'
30
+ );
31
+ }
32
+ }
33
+
34
+ for (const pattern of XSS_PATTERNS) {
35
+ const regex = new RegExp(pattern.source, pattern.flags);
36
+ let match;
37
+ while ((match = regex.exec(content)) !== null) {
38
+ const lineNum = content.substring(0, match.index).split('\n').length;
39
+ addFinding(
40
+ 'HIGH',
41
+ 'Code Vulnerabilities',
42
+ 'XSS - unsafe HTML rendering',
43
+ `File: ${relativePath}:${lineNum}\nCode: ${lines[lineNum - 1]?.trim().substring(0, 100)}`,
44
+ 'Sanitize user input before rendering as HTML. Use DOMPurify or framework-native sanitization.'
45
+ );
46
+ }
47
+ }
48
+
49
+ const evalPattern = /\beval\s*\(\s*(?!['"`])/g;
50
+ let evalMatch;
51
+ while ((evalMatch = evalPattern.exec(content)) !== null) {
52
+ const lineNum = content.substring(0, evalMatch.index).split('\n').length;
53
+ addFinding(
54
+ 'HIGH',
55
+ 'Code Vulnerabilities',
56
+ 'Dangerous eval() with dynamic input',
57
+ `File: ${relativePath}:${lineNum}`,
58
+ 'Avoid eval(). Use JSON.parse() for data, or Function constructor with strict validation.'
59
+ );
60
+ }
61
+
62
+ const regexDosPattern = /new\s+RegExp\s*\(\s*(?:req|params|query|body|user)/gi;
63
+ let regexMatch;
64
+ while ((regexMatch = regexDosPattern.exec(content)) !== null) {
65
+ const lineNum = content.substring(0, regexMatch.index).split('\n').length;
66
+ addFinding(
67
+ 'MEDIUM',
68
+ 'Code Vulnerabilities',
69
+ 'ReDoS - user input in RegExp constructor',
70
+ `File: ${relativePath}:${lineNum}`,
71
+ 'Never create regex from user input. If needed, use a safe-regex library to validate patterns.'
72
+ );
73
+ }
74
+ } catch {}
75
+ }
76
+ }
77
+
78
+ function getFiles(dir, files = []) {
79
+ try {
80
+ const entries = readdirSync(dir);
81
+ for (const entry of entries) {
82
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
83
+ const fullPath = join(dir, entry);
84
+ try {
85
+ const stat = statSync(fullPath);
86
+ if (stat.isDirectory()) getFiles(fullPath, files);
87
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) {
88
+ files.push(fullPath);
89
+ }
90
+ } catch {}
91
+ }
92
+ } catch {}
93
+ return files;
94
+ }
@@ -0,0 +1,74 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+ import { COMMAND_INJECTION_PATTERNS } from '../utils/patterns.js';
5
+
6
+ const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.rb', '.php', '.go', '.java'];
7
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
8
+
9
+ export async function auditCommandInjection(projectPath, spinner) {
10
+ spinner.text = 'Scanning for Command Injection (HackTricks)...';
11
+ const files = getFiles(projectPath);
12
+
13
+ for (const file of files) {
14
+ try {
15
+ const content = readFileSync(file, 'utf-8');
16
+ const relativePath = file.replace(projectPath, '.');
17
+ const lines = content.split('\n');
18
+
19
+ for (const pattern of COMMAND_INJECTION_PATTERNS) {
20
+ const regex = new RegExp(pattern.source, pattern.flags);
21
+ let match;
22
+ while ((match = regex.exec(content)) !== null) {
23
+ const lineNum = content.substring(0, match.index).split('\n').length;
24
+ addFinding(
25
+ 'CRITICAL',
26
+ 'Command Injection (HackTricks)',
27
+ 'OS Command Injection - user input in system command',
28
+ `File: ${relativePath}:${lineNum}\nCode: ${lines[lineNum - 1]?.trim().substring(0, 120)}`,
29
+ 'Never pass user input to shell commands. Use execFile/spawn with argument arrays instead of exec with string interpolation. Implement strict input validation with allowlists. Consider using libraries that don\'t invoke a shell.'
30
+ );
31
+ }
32
+ }
33
+
34
+ const shellPatterns = [
35
+ /os\.system\s*\(\s*f?['"].*\{/gi,
36
+ /subprocess\.(?:call|run|Popen)\s*\(\s*f?['"].*\{/gi,
37
+ /subprocess\.(?:call|run|Popen)\s*\(\s*(?:request|input)/gi,
38
+ /Runtime\.getRuntime\(\)\.exec\s*\(\s*(?:request|input)/gi,
39
+ /Process\.Start\s*\(\s*(?:request|input)/gi,
40
+ /\`[^`]*\$\{.*(?:req|params|query|body|user).*\}[^`]*\`/gi,
41
+ ];
42
+
43
+ for (const pattern of shellPatterns) {
44
+ let match;
45
+ while ((match = pattern.exec(content)) !== null) {
46
+ const lineNum = content.substring(0, match.index).split('\n').length;
47
+ addFinding(
48
+ 'CRITICAL',
49
+ 'Command Injection (HackTricks)',
50
+ 'Shell command with user-controlled input',
51
+ `File: ${relativePath}:${lineNum}\nCode: ${lines[lineNum - 1]?.trim().substring(0, 120)}`,
52
+ 'Use parameterized command execution. In Node.js: use execFile() with argument array. In Python: use subprocess with shell=False and list args.'
53
+ );
54
+ }
55
+ }
56
+ } catch {}
57
+ }
58
+ }
59
+
60
+ function getFiles(dir, files = []) {
61
+ try {
62
+ const entries = readdirSync(dir);
63
+ for (const entry of entries) {
64
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
65
+ const fullPath = join(dir, entry);
66
+ try {
67
+ const stat = statSync(fullPath);
68
+ if (stat.isDirectory()) getFiles(fullPath, files);
69
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
70
+ } catch {}
71
+ }
72
+ } catch {}
73
+ return files;
74
+ }
@@ -0,0 +1,97 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+
5
+ const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.rb', '.php', '.go', '.java', '.cs'];
6
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
7
+
8
+ export async function auditCrypto(projectPath, spinner) {
9
+ spinner.text = 'Scanning for weak cryptography (HackTricks)...';
10
+ const files = getFiles(projectPath);
11
+
12
+ for (const file of files) {
13
+ try {
14
+ const content = readFileSync(file, 'utf-8');
15
+ const relativePath = file.replace(projectPath, '.');
16
+ const lines = content.split('\n');
17
+
18
+ const weakAlgorithms = [
19
+ { pattern: /createHash\s*\(\s*['"]md5['"]\)/gi, algo: 'MD5', severity: 'HIGH' },
20
+ { pattern: /createHash\s*\(\s*['"]sha1['"]\)/gi, algo: 'SHA1', severity: 'MEDIUM' },
21
+ { pattern: /hashlib\.md5/gi, algo: 'MD5', severity: 'HIGH' },
22
+ { pattern: /hashlib\.sha1/gi, algo: 'SHA1', severity: 'MEDIUM' },
23
+ { pattern: /MessageDigest\.getInstance\s*\(\s*['"]MD5['"]\)/gi, algo: 'MD5', severity: 'HIGH' },
24
+ { pattern: /MessageDigest\.getInstance\s*\(\s*['"]SHA-?1['"]\)/gi, algo: 'SHA1', severity: 'MEDIUM' },
25
+ { pattern: /DES|3DES|RC4|RC2|Blowfish/gi, algo: 'Weak cipher', severity: 'HIGH' },
26
+ { pattern: /createCipheriv\s*\(\s*['"](?:des|rc4|aes-128-ecb)['"]/gi, algo: 'Weak cipher mode', severity: 'HIGH' },
27
+ { pattern: /ECB/g, algo: 'ECB mode', severity: 'HIGH' },
28
+ { pattern: /Math\.random\s*\(\s*\)/g, algo: 'Math.random() for crypto', severity: 'MEDIUM' },
29
+ { pattern: /random\.random\s*\(\s*\)/g, algo: 'random.random() for crypto', severity: 'MEDIUM' },
30
+ ];
31
+
32
+ for (const { pattern, algo, severity } of weakAlgorithms) {
33
+ let match;
34
+ while ((match = pattern.exec(content)) !== null) {
35
+ const lineNum = content.substring(0, match.index).split('\n').length;
36
+ const line = lines[lineNum - 1]?.trim() || '';
37
+
38
+ if (isComment(line)) continue;
39
+
40
+ addFinding(
41
+ severity,
42
+ 'Weak Cryptography (HackTricks)',
43
+ `Weak cryptographic algorithm: ${algo}`,
44
+ `File: ${relativePath}:${lineNum}\nCode: ${line.substring(0, 100)}`,
45
+ `Replace ${algo} with strong alternatives: SHA-256/SHA-3 for hashing, AES-256-GCM for encryption, crypto.randomBytes()/secrets module for random generation.`
46
+ );
47
+ }
48
+ }
49
+
50
+ const hardcodedIv = /(?:iv|nonce)\s*[:=]\s*(?:Buffer\.from|new Uint8Array)\s*\(\s*['"][^'"]+['"]\)/gi;
51
+ let ivMatch;
52
+ while ((ivMatch = hardcodedIv.exec(content)) !== null) {
53
+ const lineNum = content.substring(0, ivMatch.index).split('\n').length;
54
+ addFinding(
55
+ 'HIGH',
56
+ 'Weak Cryptography (HackTricks)',
57
+ 'Hardcoded IV/nonce detected',
58
+ `File: ${relativePath}:${lineNum}`,
59
+ 'Never hardcode IVs/nonces. Generate a new random IV for each encryption operation using crypto.randomBytes().'
60
+ );
61
+ }
62
+
63
+ const hardcodedKey = /(?:encryption|cipher|crypto).*key\s*[:=]\s*['"][A-Za-z0-9+/=]{16,}['"]/gi;
64
+ let keyMatch;
65
+ while ((keyMatch = hardcodedKey.exec(content)) !== null) {
66
+ const lineNum = content.substring(0, keyMatch.index).split('\n').length;
67
+ addFinding(
68
+ 'CRITICAL',
69
+ 'Weak Cryptography (HackTricks)',
70
+ 'Hardcoded encryption key',
71
+ `File: ${relativePath}:${lineNum}`,
72
+ 'Store encryption keys in a key management system (KMS) or environment variables. Never hardcode cryptographic keys.'
73
+ );
74
+ }
75
+ } catch {}
76
+ }
77
+ }
78
+
79
+ function isComment(line) {
80
+ return line.startsWith('//') || line.startsWith('#') || line.startsWith('*');
81
+ }
82
+
83
+ function getFiles(dir, files = []) {
84
+ try {
85
+ const entries = readdirSync(dir);
86
+ for (const entry of entries) {
87
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
88
+ const fullPath = join(dir, entry);
89
+ try {
90
+ const stat = statSync(fullPath);
91
+ if (stat.isDirectory()) getFiles(fullPath, files);
92
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
93
+ } catch {}
94
+ }
95
+ } catch {}
96
+ return files;
97
+ }
@@ -0,0 +1,83 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { addFinding } from '../core/findings.js';
5
+
6
+ export async function auditDependencies(projectPath, spinner) {
7
+ spinner.text = 'Auditing dependencies for CVEs...';
8
+
9
+ const packageJsonPath = join(projectPath, 'package.json');
10
+ const packageLockPath = join(projectPath, 'package-lock.json');
11
+
12
+ if (!existsSync(packageJsonPath)) return;
13
+
14
+ try {
15
+ const result = execSync('npm audit --json 2>/dev/null', {
16
+ cwd: projectPath,
17
+ encoding: 'utf-8',
18
+ timeout: 30000,
19
+ });
20
+
21
+ const audit = JSON.parse(result);
22
+ const vulnerabilities = audit.vulnerabilities || {};
23
+
24
+ for (const [pkg, info] of Object.entries(vulnerabilities)) {
25
+ const severity = mapSeverity(info.severity);
26
+ addFinding(
27
+ severity,
28
+ 'Dependencies',
29
+ `${pkg} has known vulnerability`,
30
+ `Severity: ${info.severity} | Via: ${info.via?.map(v => typeof v === 'string' ? v : v.title).join(', ') || 'unknown'}`,
31
+ `Run: npm audit fix, or upgrade ${pkg} to a patched version`
32
+ );
33
+ }
34
+ } catch (err) {
35
+ try {
36
+ const result = execSync('npm audit 2>&1', {
37
+ cwd: projectPath,
38
+ encoding: 'utf-8',
39
+ timeout: 30000,
40
+ });
41
+ if (result.includes('found 0 vulnerabilities')) return;
42
+
43
+ const critMatch = result.match(/(\d+)\s+critical/);
44
+ const highMatch = result.match(/(\d+)\s+high/);
45
+ const modMatch = result.match(/(\d+)\s+moderate/);
46
+
47
+ if (critMatch) {
48
+ addFinding('CRITICAL', 'Dependencies', `${critMatch[1]} critical vulnerabilities in dependencies`, 'Run npm audit for details', 'Run: npm audit fix --force');
49
+ }
50
+ if (highMatch) {
51
+ addFinding('HIGH', 'Dependencies', `${highMatch[1]} high severity vulnerabilities in dependencies`, 'Run npm audit for details', 'Run: npm audit fix');
52
+ }
53
+ if (modMatch) {
54
+ addFinding('MEDIUM', 'Dependencies', `${modMatch[1]} moderate vulnerabilities in dependencies`, 'Run npm audit for details', 'Run: npm audit fix');
55
+ }
56
+ } catch {}
57
+ }
58
+
59
+ if (existsSync(packageJsonPath)) {
60
+ try {
61
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
62
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
63
+
64
+ const dangerousPkgs = ['event-stream', 'flatmap-stream', 'ua-parser-js', 'coa', 'rc'];
65
+ for (const dangerous of dangerousPkgs) {
66
+ if (allDeps[dangerous]) {
67
+ addFinding(
68
+ 'HIGH',
69
+ 'Dependencies',
70
+ `Potentially compromised package: ${dangerous}`,
71
+ 'This package has been involved in supply-chain attacks',
72
+ `Review if ${dangerous} is necessary and check its version for known compromised versions`
73
+ );
74
+ }
75
+ }
76
+ } catch {}
77
+ }
78
+ }
79
+
80
+ function mapSeverity(npmSeverity) {
81
+ const map = { critical: 'CRITICAL', high: 'HIGH', moderate: 'MEDIUM', low: 'LOW', info: 'INFO' };
82
+ return map[npmSeverity] || 'MEDIUM';
83
+ }