ship-safe 5.0.1 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +110 -23
  2. package/cli/agents/abom-generator.js +225 -0
  3. package/cli/agents/agent-config-scanner.js +547 -0
  4. package/cli/agents/agentic-security-agent.js +1 -1
  5. package/cli/agents/api-fuzzer.js +1 -1
  6. package/cli/agents/auth-bypass-agent.js +2 -2
  7. package/cli/agents/config-auditor.js +3 -11
  8. package/cli/agents/exception-handler-agent.js +187 -0
  9. package/cli/agents/html-reporter.js +532 -370
  10. package/cli/agents/index.js +11 -1
  11. package/cli/agents/mcp-security-agent.js +182 -0
  12. package/cli/agents/pii-compliance-agent.js +4 -4
  13. package/cli/agents/scoring-engine.js +25 -6
  14. package/cli/agents/vibe-coding-agent.js +250 -0
  15. package/cli/bin/ship-safe.js +96 -6
  16. package/cli/commands/abom.js +73 -0
  17. package/cli/commands/agent.js +4 -4
  18. package/cli/commands/audit.js +15 -7
  19. package/cli/commands/baseline.js +1 -1
  20. package/cli/commands/benchmark.js +327 -0
  21. package/cli/commands/ci.js +81 -1
  22. package/cli/commands/deps.js +73 -4
  23. package/cli/commands/diff.js +200 -0
  24. package/cli/commands/doctor.js +14 -4
  25. package/cli/commands/fix.js +1 -1
  26. package/cli/commands/guard.js +99 -0
  27. package/cli/commands/init.js +407 -349
  28. package/cli/commands/openclaw.js +378 -0
  29. package/cli/commands/red-team.js +2 -2
  30. package/cli/commands/remediate.js +153 -7
  31. package/cli/commands/scan-skill.js +329 -0
  32. package/cli/commands/update-intel.js +55 -0
  33. package/cli/commands/vibe-check.js +276 -0
  34. package/cli/commands/watch.js +124 -4
  35. package/cli/data/threat-intel.json +85 -0
  36. package/cli/index.js +9 -0
  37. package/cli/utils/cache-manager.js +1 -1
  38. package/cli/utils/compliance-map.js +125 -0
  39. package/cli/utils/output.js +5 -2
  40. package/cli/utils/patterns.js +3 -0
  41. package/cli/utils/pdf-generator.js +1 -1
  42. package/cli/utils/threat-intel.js +167 -0
  43. package/package.json +2 -2
@@ -23,6 +23,10 @@ export { MCPSecurityAgent } from './mcp-security-agent.js';
23
23
  export { AgenticSecurityAgent } from './agentic-security-agent.js';
24
24
  export { RAGSecurityAgent } from './rag-security-agent.js';
25
25
  export { PIIComplianceAgent } from './pii-compliance-agent.js';
26
+ export { VibeCodingAgent } from './vibe-coding-agent.js';
27
+ export { ExceptionHandlerAgent } from './exception-handler-agent.js';
28
+ export { AgentConfigScanner } from './agent-config-scanner.js';
29
+ export { ABOMGenerator } from './abom-generator.js';
26
30
  export { VerifierAgent } from './verifier-agent.js';
27
31
  export { DeepAnalyzer } from './deep-analyzer.js';
28
32
  export { ScoringEngine, GRADES, CATEGORIES } from './scoring-engine.js';
@@ -31,7 +35,7 @@ export { PolicyEngine } from './policy-engine.js';
31
35
  export { HTMLReporter } from './html-reporter.js';
32
36
 
33
37
  /**
34
- * Create a fully configured orchestrator with all 15 scanning agents.
38
+ * Create a fully configured orchestrator with all 16 scanning agents.
35
39
  * (VerifierAgent and DeepAnalyzer run as post-processors, not in the agent pool.)
36
40
  */
37
41
  import { Orchestrator as OrchestratorClass } from './orchestrator.js';
@@ -50,6 +54,9 @@ import { MCPSecurityAgent as MCPSecurityAgentClass } from './mcp-security-agent.
50
54
  import { AgenticSecurityAgent as AgenticSecurityAgentClass } from './agentic-security-agent.js';
51
55
  import { RAGSecurityAgent as RAGSecurityAgentClass } from './rag-security-agent.js';
52
56
  import { PIIComplianceAgent as PIIComplianceAgentClass } from './pii-compliance-agent.js';
57
+ import { VibeCodingAgent as VibeCodingAgentClass } from './vibe-coding-agent.js';
58
+ import { ExceptionHandlerAgent as ExceptionHandlerAgentClass } from './exception-handler-agent.js';
59
+ import { AgentConfigScanner as AgentConfigScannerClass } from './agent-config-scanner.js';
53
60
 
54
61
  export function buildOrchestrator() {
55
62
  const orchestrator = new OrchestratorClass();
@@ -69,6 +76,9 @@ export function buildOrchestrator() {
69
76
  new AgenticSecurityAgentClass(),
70
77
  new RAGSecurityAgentClass(),
71
78
  new PIIComplianceAgentClass(),
79
+ new VibeCodingAgentClass(),
80
+ new ExceptionHandlerAgentClass(),
81
+ new AgentConfigScannerClass(),
72
82
  ]);
73
83
  return orchestrator;
74
84
  }
@@ -16,7 +16,9 @@
16
16
  * Maps to: OWASP Agentic AI ASI02 (Tool Misuse), ASI03 (Privilege Abuse)
17
17
  */
18
18
 
19
+ import fs from 'fs';
19
20
  import path from 'path';
21
+ import os from 'os';
20
22
  import { BaseAgent } from './base-agent.js';
21
23
 
22
24
  // =============================================================================
@@ -230,8 +232,26 @@ const MCP_CONFIG_FILES = [
230
232
  'mcp-config.json',
231
233
  'claude_desktop_config.json',
232
234
  '.cursor/mcp.json',
235
+ '.vscode/mcp.json',
233
236
  ];
234
237
 
238
+ // Well-known official MCP server packages
239
+ const OFFICIAL_MCP_SERVERS = new Set([
240
+ '@modelcontextprotocol/server-filesystem',
241
+ '@modelcontextprotocol/server-github',
242
+ '@modelcontextprotocol/server-gitlab',
243
+ '@modelcontextprotocol/server-google-maps',
244
+ '@modelcontextprotocol/server-memory',
245
+ '@modelcontextprotocol/server-postgres',
246
+ '@modelcontextprotocol/server-puppeteer',
247
+ '@modelcontextprotocol/server-slack',
248
+ '@modelcontextprotocol/server-sqlite',
249
+ '@modelcontextprotocol/server-brave-search',
250
+ '@modelcontextprotocol/server-fetch',
251
+ '@modelcontextprotocol/server-everything',
252
+ '@modelcontextprotocol/server-sequential-thinking',
253
+ ]);
254
+
235
255
  // =============================================================================
236
256
  // MCP SECURITY AGENT
237
257
  // =============================================================================
@@ -281,6 +301,15 @@ export class MCPSecurityAgent extends BaseAgent {
281
301
  findings = findings.concat(this._checkServerAuth(file));
282
302
  }
283
303
 
304
+ // ── 4. Check MCP configs for typosquatting & over-permissioned servers ─
305
+ for (const file of configFiles) {
306
+ findings = findings.concat(this._checkMcpTyposquatting(file));
307
+ findings = findings.concat(this._checkOverPermissioned(file));
308
+ }
309
+
310
+ // ── 5. Detect shadow MCP configs (not in version control) ───────────
311
+ findings = findings.concat(this._detectShadowMcpConfigs(rootPath));
312
+
284
313
  return findings;
285
314
  }
286
315
 
@@ -353,6 +382,159 @@ export class MCPSecurityAgent extends BaseAgent {
353
382
 
354
383
  return findings;
355
384
  }
385
+
386
+ /**
387
+ * Detect possible typosquatted MCP server names in config.
388
+ */
389
+ _checkMcpTyposquatting(filePath) {
390
+ const content = this.readFile(filePath);
391
+ if (!content) return [];
392
+ const findings = [];
393
+
394
+ try {
395
+ const config = JSON.parse(content);
396
+ const servers = config.mcpServers || config.servers || {};
397
+
398
+ for (const [name, server] of Object.entries(servers)) {
399
+ const cmd = server.command || '';
400
+ const args = (server.args || []).join(' ');
401
+ const fullCmd = `${cmd} ${args}`;
402
+
403
+ // Check if server uses an npx package that looks like a typosquat of official ones
404
+ const npxMatch = fullCmd.match(/npx\s+(?:-[^\s]+\s+)*([^\s]+)/);
405
+ if (npxMatch) {
406
+ const pkg = npxMatch[1];
407
+ for (const official of OFFICIAL_MCP_SERVERS) {
408
+ const distance = this._levenshtein(pkg, official);
409
+ if (distance > 0 && distance <= 3 && pkg !== official) {
410
+ findings.push({
411
+ file: filePath, line: 1, column: 0,
412
+ severity: 'critical',
413
+ category: this.category,
414
+ rule: 'MCP_TYPOSQUAT_SERVER',
415
+ title: `MCP: Possible Typosquatted Server "${pkg}"`,
416
+ description: `MCP server package "${pkg}" is ${distance} char(s) from official "${official}". Could be a supply chain attack.`,
417
+ matched: pkg,
418
+ confidence: 'medium',
419
+ cwe: 'CWE-494',
420
+ owasp: 'A08:2021',
421
+ fix: `Verify this is the correct package. Did you mean "${official}"?`,
422
+ });
423
+ }
424
+ }
425
+ }
426
+ }
427
+ } catch { /* not valid JSON */ }
428
+
429
+ return findings;
430
+ }
431
+
432
+ /**
433
+ * Check for over-permissioned MCP servers (filesystem access to / or ~).
434
+ */
435
+ _checkOverPermissioned(filePath) {
436
+ const content = this.readFile(filePath);
437
+ if (!content) return [];
438
+ const findings = [];
439
+
440
+ try {
441
+ const config = JSON.parse(content);
442
+ const servers = config.mcpServers || config.servers || {};
443
+
444
+ for (const [name, server] of Object.entries(servers)) {
445
+ const args = server.args || [];
446
+ const argsStr = args.join(' ');
447
+
448
+ // Check for root/home filesystem access
449
+ if (/(?:^\/\s|['" ]\/['"]?\s|\/Users\/|\/home\/|\\Users\\|C:\\)/.test(argsStr)) {
450
+ const hasWideAccess = args.some(a =>
451
+ a === '/' || a === '~' || a === '%USERPROFILE%' ||
452
+ /^\/(?:Users|home)\/[^/]+$/.test(a) ||
453
+ /^[A-Z]:\\(?:Users\\)?[^\\]*$/.test(a)
454
+ );
455
+ if (hasWideAccess) {
456
+ findings.push({
457
+ file: filePath, line: 1, column: 0,
458
+ severity: 'high',
459
+ category: this.category,
460
+ rule: 'MCP_OVER_PERMISSIONED',
461
+ title: `MCP: Server "${name}" Has Broad Filesystem Access`,
462
+ description: `MCP server "${name}" has access to a wide directory scope. A prompt injection attack could read or modify sensitive files.`,
463
+ matched: argsStr.slice(0, 200),
464
+ confidence: 'high',
465
+ cwe: 'CWE-269',
466
+ owasp: 'A01:2021',
467
+ fix: 'Restrict filesystem access to the minimum required directory: e.g., the project folder only.',
468
+ });
469
+ }
470
+ }
471
+ }
472
+ } catch { /* not valid JSON */ }
473
+
474
+ return findings;
475
+ }
476
+
477
+ /**
478
+ * Detect shadow MCP configs that exist but aren't in .gitignore or git.
479
+ */
480
+ _detectShadowMcpConfigs(rootPath) {
481
+ const findings = [];
482
+ const home = os.homedir();
483
+
484
+ // Check common locations for MCP configs outside version control
485
+ const homeConfigs = [
486
+ path.join(home, '.cursor', 'mcp.json'),
487
+ path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
488
+ path.join(home, 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'),
489
+ ];
490
+
491
+ for (const configPath of homeConfigs) {
492
+ try {
493
+ if (fs.existsSync(configPath)) {
494
+ const content = fs.readFileSync(configPath, 'utf-8');
495
+ const config = JSON.parse(content);
496
+ const servers = config.mcpServers || config.servers || {};
497
+ const serverCount = Object.keys(servers).length;
498
+
499
+ if (serverCount > 0) {
500
+ findings.push({
501
+ file: configPath, line: 1, column: 0,
502
+ severity: 'medium',
503
+ category: this.category,
504
+ rule: 'MCP_SHADOW_CONFIG',
505
+ title: `MCP: ${serverCount} Shadow Server(s) in User Config`,
506
+ description: `Found ${serverCount} MCP server(s) configured outside the project in ${configPath}. These operate outside your project's security controls.`,
507
+ matched: Object.keys(servers).join(', '),
508
+ confidence: 'medium',
509
+ cwe: 'CWE-269',
510
+ owasp: 'A05:2021',
511
+ fix: 'Review shadow MCP servers. Move project-specific servers to the project mcp.json and track in version control.',
512
+ });
513
+ }
514
+ }
515
+ } catch { /* skip */ }
516
+ }
517
+
518
+ return findings;
519
+ }
520
+
521
+ /**
522
+ * Simple Levenshtein distance for typosquatting detection.
523
+ */
524
+ _levenshtein(a, b) {
525
+ const m = a.length, n = b.length;
526
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
527
+ for (let i = 0; i <= m; i++) dp[i][0] = i;
528
+ for (let j = 0; j <= n; j++) dp[0][j] = j;
529
+ for (let i = 1; i <= m; i++) {
530
+ for (let j = 1; j <= n; j++) {
531
+ dp[i][j] = a[i-1] === b[j-1]
532
+ ? dp[i-1][j-1]
533
+ : 1 + Math.min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]);
534
+ }
535
+ }
536
+ return dp[m][n];
537
+ }
356
538
  }
357
539
 
358
540
  export default MCPSecurityAgent;
@@ -39,7 +39,7 @@ const PATTERNS = [
39
39
  {
40
40
  rule: 'PII_IN_LOGGER',
41
41
  title: 'Privacy: PII in Structured Logger',
42
- regex: /(?:logger|log|winston|pino|bunyan|morgan)\.(?:info|warn|error|debug|log)\s*\([\s\S]{0,200}(?:email|password|ssn|creditCard|credit_card|phoneNumber|phone_number|dateOfBirth|date_of_birth)/g,
42
+ regex: /(?:logger|winston|pino|bunyan|morgan)\.(?:info|warn|error|debug|log)\s*\([\s\S]{0,200}(?:[\.\[\(]email\b|password|ssn|creditCard|credit_card|phoneNumber|phone_number|dateOfBirth|date_of_birth)/g,
43
43
  severity: 'high',
44
44
  cwe: 'CWE-532',
45
45
  owasp: 'A09:2021',
@@ -62,7 +62,7 @@ const PATTERNS = [
62
62
  {
63
63
  rule: 'PII_IN_ERROR_RESPONSE',
64
64
  title: 'Privacy: PII in Error Response to Client',
65
- regex: /(?:res\.(?:json|send|status)|response\.(?:json|send)|jsonify)\s*\(\s*(?:\{[\s\S]{0,200}(?:email|password|ssn|creditCard|phone|user|customer|patient)[\s\S]{0,100}\}|err|error)/g,
65
+ regex: /(?:res\.(?:json|send|status)|response\.(?:json|send)|jsonify)\s*\(\s*(?:\{[\s\S]{0,200}(?:email|password|ssn|creditCard|credit_card|phoneNumber|phone_number|patient|dateOfBirth|date_of_birth)[\s\S]{0,100}\}|err(?:or)?\.(?:stack|message))/g,
66
66
  severity: 'high',
67
67
  cwe: 'CWE-209',
68
68
  owasp: 'A01:2021',
@@ -151,7 +151,7 @@ const PATTERNS = [
151
151
  {
152
152
  rule: 'PII_EMAIL_HARDCODED',
153
153
  title: 'Privacy: Real Email Address Hardcoded',
154
- regex: /['"][a-zA-Z0-9._%+-]+@(?!example\.com|test\.com|placeholder|fake|dummy)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}['"]/g,
154
+ regex: /['"][a-zA-Z0-9._%+-]+@(?!example\.com|example\.org|test\.com|test\.org|testing\.com|localhost|placeholder|fake|dummy|mailinator\.com|noreply|no-reply|sample\.com)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}['"]/g,
155
155
  severity: 'medium',
156
156
  cwe: 'CWE-312',
157
157
  owasp: 'A02:2021',
@@ -199,7 +199,7 @@ const PATTERNS = [
199
199
  {
200
200
  rule: 'PII_GEOLOCATION_STORAGE',
201
201
  title: 'Privacy: Precise Geolocation Stored',
202
- regex: /(?:latitude|longitude|lat|lng|geolocation|geoip|geo_location)[\s\S]{0,100}(?:save|store|insert|create|update|write|database|db|mongo|prisma|sequelize)/gi,
202
+ regex: /(?:latitude|longitude|\blat\b|\blng\b|geolocation|geoip|geo_location)[\s\S]{0,100}(?:save|store|insert|create|update|write|database|db|mongo|prisma|sequelize)/gi,
203
203
  severity: 'medium',
204
204
  cwe: 'CWE-359',
205
205
  owasp: 'A01:2021',
@@ -11,27 +11,34 @@
11
11
 
12
12
  import fs from 'fs';
13
13
  import path from 'path';
14
+ import { getComplianceSummary } from '../utils/compliance-map.js';
14
15
 
15
16
  // =============================================================================
16
17
  // SCORING CONFIGURATION
17
18
  // =============================================================================
18
19
 
20
+ // Weights aligned with OWASP Top 10 2025:
21
+ // A01 Broken Access Control (auth: 15), A02 Security Misconfiguration (config: 8),
22
+ // A03 Software Supply Chain Failures (supply-chain: 12, deps: 13),
23
+ // A05 Injection (injection: 15), A07 Auth Failures (secrets: 15),
24
+ // A10 Mishandling of Exceptional Conditions (→ injection category)
25
+ // + API Security (10), AI/LLM Security (12) — weights sum to 100
19
26
  const CATEGORIES = {
20
27
  secrets: { weight: 15, label: 'Secrets', deductions: { critical: 25, high: 15, medium: 5 } },
21
28
  injection: { weight: 15, label: 'Code Vulnerabilities', deductions: { critical: 20, high: 10, medium: 3 } },
22
- deps: { weight: 15, label: 'Dependencies', deductions: { critical: 20, high: 10, medium: 5, moderate: 5 } },
29
+ deps: { weight: 13, label: 'Dependencies', deductions: { critical: 20, high: 10, medium: 5, moderate: 5 } },
23
30
  auth: { weight: 15, label: 'Auth & Access Control', deductions: { critical: 20, high: 10, medium: 3 } },
24
- config: { weight: 10, label: 'Configuration', deductions: { critical: 15, high: 8, medium: 3 } },
25
- 'supply-chain':{ weight: 10, label: 'Supply Chain', deductions: { critical: 15, high: 8, medium: 3 } },
31
+ config: { weight: 8, label: 'Configuration', deductions: { critical: 15, high: 8, medium: 3 } },
32
+ 'supply-chain':{ weight: 12, label: 'Supply Chain', deductions: { critical: 15, high: 8, medium: 3 } },
26
33
  api: { weight: 10, label: 'API Security', deductions: { critical: 15, high: 8, medium: 3 } },
27
- llm: { weight: 10, label: 'AI/LLM Security', deductions: { critical: 15, high: 8, medium: 3 } },
34
+ llm: { weight: 12, label: 'AI/LLM Security', deductions: { critical: 15, high: 8, medium: 3 } },
28
35
  };
29
36
 
30
37
  // Fallback categories for findings that don't match a known category
31
38
  const FALLBACK_CATEGORY_MAP = {
32
39
  'secret': 'secrets',
33
40
  'vulnerability': 'injection',
34
- 'ssrf': 'injection',
41
+ 'ssrf': 'injection', // OWASP 2025: SSRF merged into A01 Broken Access Control
35
42
  'history': 'secrets',
36
43
  'cicd': 'config',
37
44
  'mobile': 'injection',
@@ -39,7 +46,10 @@ const FALLBACK_CATEGORY_MAP = {
39
46
  'mcp': 'llm',
40
47
  'agentic': 'llm',
41
48
  'rag': 'llm',
42
- 'recon': null, // skip recon findings
49
+ 'vibe': 'injection', // Vibe coding findings → Code Vulnerabilities
50
+ 'exception': 'injection', // OWASP A10:2025 — Mishandling of Exceptional Conditions
51
+ 'agent-config': 'llm', // Agent config security → AI/LLM category
52
+ 'recon': null, // skip recon findings
43
53
  };
44
54
 
45
55
  const GRADES = [
@@ -128,12 +138,21 @@ export class ScoringEngine {
128
138
  const score = Math.max(0, 100 - totalDeduction);
129
139
  const grade = GRADES.find(g => score >= g.min);
130
140
 
141
+ // ── Compliance mapping ─────────────────────────────────────────────────
142
+ let compliance;
143
+ try {
144
+ compliance = getComplianceSummary(findings);
145
+ } catch {
146
+ compliance = null;
147
+ }
148
+
131
149
  return {
132
150
  score,
133
151
  grade,
134
152
  categories: categoryResults,
135
153
  totalFindings: findings.length,
136
154
  totalDepVulns: depVulns.length,
155
+ compliance,
137
156
  };
138
157
  }
139
158
 
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Vibe Coding Security Agent
3
+ * ============================
4
+ *
5
+ * Detects security vulnerabilities commonly introduced by
6
+ * AI-generated code (Cursor, Copilot, Claude Code, etc.).
7
+ *
8
+ * 45% of AI-generated code contains security vulnerabilities.
9
+ * This agent targets the most frequent patterns:
10
+ *
11
+ * - Placeholder credentials left in code
12
+ * - Missing input validation on AI-generated routes
13
+ * - Overly permissive defaults (CORS *, DEBUG=True)
14
+ * - Unprotected API endpoints (no auth middleware)
15
+ * - TODO/FIXME security markers left behind
16
+ * - Hardcoded localhost/development URLs in production code
17
+ * - Incomplete error handling (empty catch blocks)
18
+ * - AI-generated boilerplate with known insecure patterns
19
+ *
20
+ * Maps to: OWASP A01 (Broken Access Control), A05 (Security Misconfiguration)
21
+ */
22
+
23
+ import path from 'path';
24
+ import { BaseAgent, createFinding } from './base-agent.js';
25
+
26
+ // =============================================================================
27
+ // VIBE CODING PATTERNS
28
+ // =============================================================================
29
+
30
+ const PATTERNS = [
31
+ // ── Placeholder Credentials ───────────────────────────────────────────────
32
+ {
33
+ rule: 'VIBE_PLACEHOLDER_KEY',
34
+ title: 'Vibe Code: Placeholder API Key Left in Code',
35
+ regex: /(?:api[_-]?key|apikey|secret|token|password)\s*[:=]\s*['"](?:your[_-]?(?:api[_-]?)?key[_-]?here|sk[_-](?:test|live)[_-]x{5,}|xxx+|insert[_-]?(?:your[_-]?)?(?:key|token|secret)[_-]?here|change[_-]?me|replace[_-]?(?:this|me)|put[_-]?your[_-]?(?:key|token))['"]/gi,
36
+ severity: 'critical',
37
+ cwe: 'CWE-798',
38
+ owasp: 'A07:2021',
39
+ description: 'AI-generated placeholder credential left in code. These are commonly generated by Copilot/Cursor and forgotten before deploy.',
40
+ fix: 'Replace with environment variable: process.env.API_KEY or os.environ["API_KEY"]',
41
+ },
42
+ {
43
+ rule: 'VIBE_PLACEHOLDER_URL',
44
+ title: 'Vibe Code: Placeholder URL Left in Code',
45
+ regex: /['"]https?:\/\/(?:your[_-]?(?:domain|api|server|backend)|example\.com\/api|api\.example|placeholder)[^'"]*['"]/gi,
46
+ severity: 'medium',
47
+ cwe: 'CWE-1188',
48
+ owasp: 'A05:2021',
49
+ confidence: 'medium',
50
+ description: 'AI-generated placeholder URL left in code. May cause requests to fail or leak data to unintended endpoints.',
51
+ fix: 'Replace with environment variable for the API URL',
52
+ },
53
+
54
+ // ── Security TODO/FIXME Markers ───────────────────────────────────────────
55
+ {
56
+ rule: 'VIBE_TODO_AUTH',
57
+ title: 'Vibe Code: TODO — Add Authentication',
58
+ regex: /\/\/\s*(?:TODO|FIXME|HACK|XXX)\s*:?\s*(?:add|implement|fix|enable|require)\s+(?:auth(?:entication|orization)?|login|session|jwt|token|middleware|permission|rbac|access.?control)/gi,
59
+ severity: 'high',
60
+ cwe: 'CWE-306',
61
+ owasp: 'A01:2021',
62
+ description: 'Code has a TODO marker to add authentication/authorization. AI-generated code often ships without auth.',
63
+ fix: 'Implement authentication before deploying. This TODO indicates a known security gap.',
64
+ },
65
+ {
66
+ rule: 'VIBE_TODO_VALIDATION',
67
+ title: 'Vibe Code: TODO — Add Input Validation',
68
+ regex: /\/\/\s*(?:TODO|FIXME|HACK|XXX)\s*:?\s*(?:add|implement|fix|enable)\s+(?:valid(?:ation|ate)|sanitiz(?:e|ation)|escap(?:e|ing)|input.?check|type.?check)/gi,
69
+ severity: 'high',
70
+ cwe: 'CWE-20',
71
+ owasp: 'A03:2021',
72
+ description: 'Code has a TODO marker to add input validation. AI-generated endpoints frequently skip validation.',
73
+ fix: 'Implement input validation before deploying. Unvalidated input leads to injection attacks.',
74
+ },
75
+ {
76
+ rule: 'VIBE_TODO_SECURITY',
77
+ title: 'Vibe Code: TODO — Security Fix Needed',
78
+ regex: /\/\/\s*(?:TODO|FIXME|HACK|XXX)\s*:?\s*(?:add|implement|fix|enable|remove)\s+(?:security|encrypt(?:ion)?|hash(?:ing)?|rate.?limit|csrf|xss|cors|https|ssl|tls|firewall)/gi,
79
+ severity: 'high',
80
+ cwe: 'CWE-693',
81
+ owasp: 'A05:2021',
82
+ description: 'Security-related TODO left in code. This marks a known vulnerability the developer intended to fix.',
83
+ fix: 'Address this security TODO before deploying to production.',
84
+ },
85
+
86
+ // ── Missing Auth on Routes ────────────────────────────────────────────────
87
+ {
88
+ rule: 'VIBE_UNPROTECTED_DELETE',
89
+ title: 'Vibe Code: DELETE Endpoint Without Auth Check',
90
+ regex: /(?:app|router)\.(?:delete)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:async\s+)?\(?(?:req|request|ctx)\b/g,
91
+ severity: 'high',
92
+ cwe: 'CWE-306',
93
+ owasp: 'A01:2021',
94
+ confidence: 'medium',
95
+ description: 'DELETE endpoint defined without visible auth middleware. AI often generates CRUD routes without access control.',
96
+ fix: 'Add authentication middleware: router.delete("/resource/:id", authMiddleware, handler)',
97
+ },
98
+ {
99
+ rule: 'VIBE_UNPROTECTED_ADMIN',
100
+ title: 'Vibe Code: Admin Route Without Auth Middleware',
101
+ regex: /(?:app|router)\.(?:get|post|put|patch|delete)\s*\(\s*['"]\/admin[^'"]*['"]\s*,\s*(?:async\s+)?\(?(?:req|request|ctx)\b/g,
102
+ severity: 'critical',
103
+ cwe: 'CWE-306',
104
+ owasp: 'A01:2021',
105
+ confidence: 'medium',
106
+ description: 'Admin route defined without visible auth middleware. AI-generated admin panels often lack access control.',
107
+ fix: 'Add auth + admin role check: router.get("/admin", authMiddleware, requireAdmin, handler)',
108
+ },
109
+
110
+ // ── Insecure Defaults ─────────────────────────────────────────────────────
111
+ {
112
+ rule: 'VIBE_WILDCARD_CORS',
113
+ title: 'Vibe Code: CORS Allow All Origins',
114
+ regex: /cors\s*\(\s*\{?\s*(?:origin\s*:\s*(?:true|['"]?\*['"]?)|(?:origin\s*:\s*\[?\s*['"]?\*['"]?))/g,
115
+ severity: 'high',
116
+ cwe: 'CWE-942',
117
+ owasp: 'A05:2021',
118
+ description: 'CORS configured to allow all origins. AI tools frequently generate cors({ origin: "*" }) or cors({ origin: true }) as defaults.',
119
+ fix: 'Restrict to specific origins: cors({ origin: ["https://yourdomain.com"] })',
120
+ },
121
+ {
122
+ rule: 'VIBE_DEBUG_PRODUCTION',
123
+ title: 'Vibe Code: Debug Mode Enabled',
124
+ regex: /(?:DEBUG|debug)\s*[:=]\s*(?:true|True|1|['"]true['"])\s*(?:,|\n|$|;)/g,
125
+ severity: 'high',
126
+ cwe: 'CWE-489',
127
+ owasp: 'A05:2021',
128
+ confidence: 'medium',
129
+ description: 'Debug mode is enabled. AI-generated configs often leave DEBUG=True from development.',
130
+ fix: 'Set DEBUG=False for production. Use environment variable: DEBUG=os.environ.get("DEBUG", "False")',
131
+ },
132
+ // TLS disabled detection is handled by AuthBypassAgent (NODE_TLS_REJECT_UNAUTHORIZED)
133
+ // and ConfigAuditor — not duplicated here to avoid false positives on regex definitions.
134
+
135
+ // ── Hardcoded Development URLs ────────────────────────────────────────────
136
+ {
137
+ rule: 'VIBE_HARDCODED_LOCALHOST',
138
+ title: 'Vibe Code: Hardcoded localhost URL in Production Code',
139
+ regex: /(?:fetch|axios|http|request|api|endpoint|baseURL|base_url|API_URL|BACKEND_URL)\s*[:=(]\s*['"]https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0):\d+[^'"]*['"]/gi,
140
+ severity: 'medium',
141
+ cwe: 'CWE-1188',
142
+ owasp: 'A05:2021',
143
+ confidence: 'medium',
144
+ description: 'Hardcoded localhost URL found. AI-generated code often hardcodes development URLs that break in production.',
145
+ fix: 'Use environment variable: process.env.API_URL || "http://localhost:3000"',
146
+ },
147
+
148
+ // Empty catch blocks and except: pass are handled by ExceptionHandlerAgent (OWASP A10:2025)
149
+
150
+ // ── AI-Generated Insecure Patterns ────────────────────────────────────────
151
+ {
152
+ rule: 'VIBE_EVAL_INPUT',
153
+ title: 'Vibe Code: eval() with Variable Input',
154
+ regex: /\beval\s*\(\s*(?:req\.|request\.|body\.|params\.|query\.|input|data|payload|args|user)/g,
155
+ severity: 'critical',
156
+ cwe: 'CWE-95',
157
+ owasp: 'A03:2021',
158
+ description: 'eval() called with user-controlled input. AI sometimes generates eval() for "dynamic" functionality.',
159
+ fix: 'Never use eval() with user input. Use a safe parser or allowlist of operations.',
160
+ },
161
+ {
162
+ rule: 'VIBE_INNER_HTML',
163
+ title: 'Vibe Code: innerHTML with Variable',
164
+ regex: /\.innerHTML\s*=\s*(?!['"]<)[^;]+(?:req|request|input|data|user|response|result|body|params|query|payload)/g,
165
+ severity: 'high',
166
+ cwe: 'CWE-79',
167
+ owasp: 'A03:2021',
168
+ description: 'innerHTML set with dynamic content. AI-generated frontend code often skips XSS sanitization.',
169
+ fix: 'Use textContent for plain text, or sanitize with DOMPurify: el.innerHTML = DOMPurify.sanitize(html)',
170
+ },
171
+ {
172
+ rule: 'VIBE_NO_PARAMETERIZED_QUERY',
173
+ title: 'Vibe Code: String Concatenation in SQL',
174
+ regex: /(?:query|execute|raw|exec)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+\s*(?:req|request|body|params|query|input|user|data))/g,
175
+ severity: 'critical',
176
+ cwe: 'CWE-89',
177
+ owasp: 'A03:2021',
178
+ description: 'SQL query built with string concatenation/interpolation. AI-generated database code frequently lacks parameterization.',
179
+ fix: 'Use parameterized queries: db.query("SELECT * FROM users WHERE id = $1", [userId])',
180
+ },
181
+
182
+ // ── Missing Rate Limiting ─────────────────────────────────────────────────
183
+ {
184
+ rule: 'VIBE_NO_RATE_LIMIT',
185
+ title: 'Vibe Code: Auth Endpoint Without Rate Limiting',
186
+ regex: /(?:app|router)\.post\s*\(\s*['"]\/(?:login|signin|sign-in|register|signup|sign-up|forgot-password|reset-password|auth\/login)['"]\s*,\s*(?:async\s+)?\(?(?:req|request|ctx)\b/g,
187
+ severity: 'high',
188
+ cwe: 'CWE-307',
189
+ owasp: 'A07:2021',
190
+ confidence: 'medium',
191
+ description: 'Authentication endpoint without visible rate limiting. AI-generated auth routes rarely include brute-force protection.',
192
+ fix: 'Add rate limiting: app.post("/login", rateLimit({ windowMs: 15*60*1000, max: 5 }), handler)',
193
+ },
194
+
195
+ // ── Insecure File Operations ──────────────────────────────────────────────
196
+ {
197
+ rule: 'VIBE_PATH_TRAVERSAL',
198
+ title: 'Vibe Code: User Input in File Path',
199
+ regex: /(?:readFile|writeFile|createReadStream|createWriteStream|open|access)\s*\(\s*(?:req\.|request\.|body\.|params\.|query\.|`[^`]*\$\{(?:req|request|params|query|body))/g,
200
+ severity: 'critical',
201
+ cwe: 'CWE-22',
202
+ owasp: 'A01:2021',
203
+ description: 'File operation uses user-controlled input without path validation. AI-generated file serving code often allows path traversal.',
204
+ fix: 'Validate and sanitize the path: path.resolve(baseDir, path.basename(userInput))',
205
+ },
206
+
207
+ // ── Exposed Secrets in Client Code ────────────────────────────────────────
208
+ {
209
+ rule: 'VIBE_SECRET_IN_FRONTEND',
210
+ title: 'Vibe Code: Secret Key in Frontend/Client Code',
211
+ regex: /(?:NEXT_PUBLIC_|REACT_APP_|VITE_|NUXT_PUBLIC_|EXPO_PUBLIC_)(?:SECRET|PRIVATE|API_SECRET|DATABASE_URL|DB_PASSWORD|JWT_SECRET|STRIPE_SECRET)/gi,
212
+ severity: 'critical',
213
+ cwe: 'CWE-200',
214
+ owasp: 'A01:2021',
215
+ description: 'Secret exposed via public environment variable prefix. AI tools often use NEXT_PUBLIC_/REACT_APP_ for all env vars.',
216
+ fix: 'Remove the public prefix. Server-side secrets must NOT use NEXT_PUBLIC_, REACT_APP_, or VITE_ prefixes.',
217
+ },
218
+ ];
219
+
220
+ // =============================================================================
221
+ // VIBE CODING AGENT CLASS
222
+ // =============================================================================
223
+
224
+ export class VibeCodingAgent extends BaseAgent {
225
+ constructor() {
226
+ super(
227
+ 'VibeCodingAgent',
228
+ 'Detects security vulnerabilities commonly introduced by AI-generated code',
229
+ 'injection', // maps to Code Vulnerabilities in scoring
230
+ );
231
+ }
232
+
233
+ async analyze(context) {
234
+ const files = this.getFilesToScan(context);
235
+ const findings = [];
236
+
237
+ for (const file of files) {
238
+ const ext = path.extname(file).toLowerCase();
239
+ // Only scan source code files
240
+ if (!['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
241
+ '.py', '.rb', '.go', '.java', '.rs', '.php',
242
+ '.vue', '.svelte', '.astro'].includes(ext)) continue;
243
+
244
+ const results = this.scanFileWithPatterns(file, PATTERNS);
245
+ findings.push(...results);
246
+ }
247
+
248
+ return findings;
249
+ }
250
+ }