ship-safe 6.1.0 → 6.2.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 (48) hide show
  1. package/README.md +735 -594
  2. package/cli/agents/api-fuzzer.js +345 -345
  3. package/cli/agents/auth-bypass-agent.js +348 -348
  4. package/cli/agents/base-agent.js +272 -272
  5. package/cli/agents/cicd-scanner.js +236 -201
  6. package/cli/agents/config-auditor.js +521 -521
  7. package/cli/agents/deep-analyzer.js +6 -2
  8. package/cli/agents/git-history-scanner.js +170 -170
  9. package/cli/agents/html-reporter.js +40 -4
  10. package/cli/agents/index.js +84 -84
  11. package/cli/agents/injection-tester.js +500 -500
  12. package/cli/agents/llm-redteam.js +251 -251
  13. package/cli/agents/mobile-scanner.js +231 -231
  14. package/cli/agents/orchestrator.js +322 -322
  15. package/cli/agents/pii-compliance-agent.js +301 -301
  16. package/cli/agents/scoring-engine.js +248 -248
  17. package/cli/agents/supabase-rls-agent.js +154 -154
  18. package/cli/agents/supply-chain-agent.js +650 -507
  19. package/cli/bin/ship-safe.js +452 -426
  20. package/cli/commands/agent.js +608 -608
  21. package/cli/commands/audit.js +986 -979
  22. package/cli/commands/baseline.js +193 -193
  23. package/cli/commands/ci.js +342 -342
  24. package/cli/commands/deps.js +516 -516
  25. package/cli/commands/doctor.js +159 -159
  26. package/cli/commands/fix.js +218 -218
  27. package/cli/commands/hooks.js +268 -0
  28. package/cli/commands/init.js +407 -407
  29. package/cli/commands/mcp.js +304 -304
  30. package/cli/commands/red-team.js +7 -1
  31. package/cli/commands/remediate.js +798 -798
  32. package/cli/commands/rotate.js +571 -571
  33. package/cli/commands/scan.js +569 -567
  34. package/cli/commands/score.js +449 -448
  35. package/cli/commands/watch.js +281 -281
  36. package/cli/hooks/patterns.js +313 -0
  37. package/cli/hooks/post-tool-use.js +140 -0
  38. package/cli/hooks/pre-tool-use.js +186 -0
  39. package/cli/index.js +73 -69
  40. package/cli/providers/llm-provider.js +397 -287
  41. package/cli/utils/autofix-rules.js +74 -74
  42. package/cli/utils/cache-manager.js +311 -311
  43. package/cli/utils/output.js +1 -0
  44. package/cli/utils/patterns.js +1121 -1121
  45. package/cli/utils/pdf-generator.js +94 -94
  46. package/package.json +69 -68
  47. package/cli/__tests__/agents.test.js +0 -1301
  48. package/configs/supabase/rls-templates.sql +0 -242
@@ -1,345 +1,345 @@
1
- /**
2
- * APIFuzzer Agent
3
- * ================
4
- *
5
- * Static analysis of API endpoints for security anti-patterns.
6
- * Checks authentication, authorization, input validation,
7
- * rate limiting, CORS, error handling, data exposure,
8
- * mass assignment, GraphQL, file uploads, and pagination.
9
- */
10
-
11
- import path from 'path';
12
- import { BaseAgent, createFinding } from './base-agent.js';
13
-
14
- const PATTERNS = [
15
- // ── Missing Authentication ─────────────────────────────────────────────────
16
- {
17
- rule: 'API_NO_AUTH_CHECK',
18
- title: 'API: Route Without Auth Check',
19
- regex: /(?:app|router)\.(?:post|put|patch|delete)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:async\s+)?(?:\(req|function)/g,
20
- severity: 'high',
21
- cwe: 'CWE-306',
22
- owasp: 'A07:2021',
23
- confidence: 'medium',
24
- description: 'State-changing API route without visible auth middleware. Verify authentication is enforced.',
25
- fix: 'Add auth middleware: router.post("/api/data", authMiddleware, handler)',
26
- },
27
-
28
- // ── Input Validation ───────────────────────────────────────────────────────
29
- {
30
- rule: 'API_NO_VALIDATION',
31
- title: 'API: No Input Validation',
32
- regex: /(?:req\.body|request\.body|ctx\.request\.body)\s*(?:;|\))/g,
33
- severity: 'medium',
34
- cwe: 'CWE-20',
35
- owasp: 'A03:2021',
36
- confidence: 'low',
37
- description: 'Request body used without validation. Validate with Zod, Joi, or Yup.',
38
- fix: 'Validate: const data = schema.parse(req.body) using Zod or similar',
39
- },
40
- {
41
- rule: 'API_SPREAD_BODY',
42
- title: 'API: Spread Request Body into Operation',
43
- regex: /\.\.\.\s*(?:req\.body|request\.body|ctx\.request\.body)/g,
44
- severity: 'high',
45
- cwe: 'CWE-915',
46
- owasp: 'A01:2021',
47
- description: 'Spreading request body enables mass assignment. Only use allowed fields.',
48
- fix: 'Destructure specific fields: const { name, email } = req.body',
49
- },
50
-
51
- // ── Error Handling ─────────────────────────────────────────────────────────
52
- {
53
- rule: 'API_STACK_TRACE_RESPONSE',
54
- title: 'API: Stack Trace in Response',
55
- regex: /(?:res\.(?:json|send|status)|ctx\.body)\s*\(\s*(?:\{[^}]*(?:err\.stack|error\.stack|err\.message|error\.message)|err\b|error\b)/g,
56
- severity: 'medium',
57
- cwe: 'CWE-209',
58
- owasp: 'A05:2021',
59
- description: 'Error details sent in API response leak internal information.',
60
- fix: 'Log errors server-side. Return generic message: res.status(500).json({ error: "Internal error" })',
61
- },
62
-
63
- // ── Data Exposure ──────────────────────────────────────────────────────────
64
- {
65
- rule: 'API_EXCESSIVE_DATA',
66
- title: 'API: Returning Full Database Object',
67
- regex: /(?:res\.json|res\.send|ctx\.body)\s*\(\s*(?:user|users|record|result|data|row|document)\s*\)/g,
68
- severity: 'medium',
69
- cwe: 'CWE-200',
70
- owasp: 'A01:2021',
71
- confidence: 'low',
72
- description: 'Returning full DB objects may expose sensitive fields (password, email, etc.).',
73
- fix: 'Select specific fields: res.json({ id: user.id, name: user.name })',
74
- },
75
-
76
- // ── File Upload ────────────────────────────────────────────────────────────
77
- {
78
- rule: 'API_UNRESTRICTED_UPLOAD',
79
- title: 'API: Unrestricted File Upload',
80
- regex: /(?:multer|formidable|busboy|multiparty)\s*\(/g,
81
- severity: 'medium',
82
- cwe: 'CWE-434',
83
- owasp: 'A04:2021',
84
- confidence: 'low',
85
- description: 'File upload handler detected. Ensure file type validation, size limits, and secure storage.',
86
- fix: 'Add: fileFilter, limits: { fileSize: 5*1024*1024 }, and validate MIME type',
87
- },
88
- {
89
- rule: 'API_UPLOAD_NO_TYPE_CHECK',
90
- title: 'API: File Upload Without Type Validation',
91
- regex: /(?<!_)(?:originalname|filename)\s*(?:\)|;)/g,
92
- severity: 'high',
93
- cwe: 'CWE-434',
94
- owasp: 'A04:2021',
95
- confidence: 'low',
96
- description: 'File upload using original filename without type validation.',
97
- fix: 'Validate file extension and MIME type. Generate random filenames for storage.',
98
- },
99
- {
100
- rule: 'API_PATH_IN_FILENAME',
101
- title: 'API: Path Traversal in File Upload',
102
- regex: /path\.join\s*\([^)]*(?:originalname|filename|req\.file|req\.body)/g,
103
- severity: 'critical',
104
- cwe: 'CWE-22',
105
- owasp: 'A01:2021',
106
- description: 'User-supplied filename in path construction enables directory traversal.',
107
- fix: 'Generate random filename: crypto.randomUUID() + path.extname(file.originalname)',
108
- },
109
-
110
- // ── GraphQL Security ───────────────────────────────────────────────────────
111
- {
112
- rule: 'GRAPHQL_INTROSPECTION',
113
- title: 'GraphQL: Introspection Enabled',
114
- regex: /introspection\s*:\s*true/g,
115
- severity: 'medium',
116
- cwe: 'CWE-200',
117
- owasp: 'A05:2021',
118
- description: 'GraphQL introspection enabled. Exposes full schema to attackers in production.',
119
- fix: 'Disable in production: introspection: process.env.NODE_ENV !== "production"',
120
- },
121
- {
122
- rule: 'GRAPHQL_NO_DEPTH_LIMIT',
123
- title: 'GraphQL: No Query Depth Limit',
124
- regex: /(?:ApolloServer|GraphQLServer|createYoga|makeExecutableSchema)\s*\(/g,
125
- severity: 'medium',
126
- cwe: 'CWE-400',
127
- confidence: 'low',
128
- description: 'GraphQL server without query depth limiting. Enables deeply nested DoS queries.',
129
- fix: 'Add depth limiting: graphql-depth-limit or @escape.tech/graphql-armor',
130
- },
131
- {
132
- rule: 'GRAPHQL_NO_COST_ANALYSIS',
133
- title: 'GraphQL: No Query Cost Analysis',
134
- regex: /(?:typeDefs|schema)\s*[:=].*(?:Query|Mutation)\s*\{/g,
135
- severity: 'low',
136
- cwe: 'CWE-400',
137
- confidence: 'low',
138
- description: 'GraphQL schema without query cost analysis. Complex queries can cause DoS.',
139
- fix: 'Add query complexity analysis: graphql-query-complexity or graphql-armor',
140
- },
141
-
142
- // ── API Versioning & Documentation ─────────────────────────────────────────
143
- {
144
- rule: 'API_DEBUG_ENDPOINT',
145
- title: 'API: Debug/Test Endpoint in Code',
146
- regex: /(?:app|router)\.(?:get|post|all)\s*\(\s*['"]\/(?:debug|test|admin|internal|_internal|healthcheck\/debug)/gi,
147
- severity: 'high',
148
- cwe: 'CWE-489',
149
- owasp: 'A05:2021',
150
- description: 'Debug/test/admin endpoint detected. Ensure it is not accessible in production.',
151
- fix: 'Remove debug endpoints or protect with auth + environment check',
152
- },
153
-
154
- // ── Response Headers ───────────────────────────────────────────────────────
155
- {
156
- rule: 'API_NO_SECURITY_HEADERS',
157
- title: 'API: Missing Security Headers (Helmet)',
158
- regex: /app\.(?:use|listen)\s*\(/g,
159
- severity: 'low',
160
- cwe: 'CWE-693',
161
- owasp: 'A05:2021',
162
- confidence: 'low',
163
- description: 'Express app without helmet middleware. Missing security headers (CSP, HSTS, etc.).',
164
- fix: 'Add helmet: app.use(helmet()) for automatic security headers',
165
- },
166
-
167
- // ── Sensitive Data in URL ──────────────────────────────────────────────────
168
- {
169
- rule: 'API_KEY_IN_URL',
170
- title: 'API: Secret in URL Query Parameter',
171
- regex: /(?:url|endpoint|href)\s*[:=]\s*[`"'][^`"']*\?[^`"']*(?:key|token|secret|password|apiKey|api_key)\s*=/gi,
172
- severity: 'high',
173
- cwe: 'CWE-598',
174
- owasp: 'A02:2021',
175
- description: 'API key or secret passed in URL query parameter. URLs are logged in server logs, browser history, and proxies.',
176
- fix: 'Move secrets to request headers (e.g., Authorization, x-api-key) instead of URL parameters.',
177
- },
178
- {
179
- rule: 'API_SECRET_IN_URL',
180
- title: 'API: Sensitive Data in URL Parameters',
181
- regex: /(?:app|router)\.(?:get|post)\s*\(\s*['"][^'"]*(?::token|:apiKey|:password|:secret|:key)\b/g,
182
- severity: 'high',
183
- cwe: 'CWE-598',
184
- owasp: 'A04:2021',
185
- description: 'Sensitive data in URL parameters gets logged in server logs, browser history, and proxies.',
186
- fix: 'Move sensitive data to request headers or body',
187
- },
188
-
189
- // ── Server Configuration ───────────────────────────────────────────────────
190
- {
191
- rule: 'API_TRUST_PROXY',
192
- title: 'API: Trust Proxy Not Configured',
193
- regex: /app\.set\s*\(\s*['"]trust proxy['"]\s*,\s*true\s*\)/g,
194
- severity: 'low',
195
- cwe: 'CWE-346',
196
- confidence: 'low',
197
- description: 'trust proxy set to true trusts all proxies. Specify trusted proxy IPs.',
198
- fix: 'Set specific proxy: app.set("trust proxy", "loopback") or IP address',
199
- },
200
-
201
- // ── Denial of Service ──────────────────────────────────────────────────────
202
- {
203
- rule: 'API_LARGE_BODY_NO_LIMIT',
204
- title: 'API: No Request Body Size Limit',
205
- regex: /(?:express\.json|bodyParser\.json)\s*\(\s*\)/g,
206
- severity: 'medium',
207
- cwe: 'CWE-400',
208
- confidence: 'low',
209
- description: 'No body size limit configured. Large payloads can cause memory exhaustion.',
210
- fix: 'Set limit: express.json({ limit: "1mb" })',
211
- },
212
- {
213
- rule: 'API_NO_PAGINATION',
214
- title: 'API: Query Without Limit',
215
- regex: /\.find\s*\(\s*\{?\s*\}\s*\)/g,
216
- severity: 'low',
217
- cwe: 'CWE-400',
218
- confidence: 'low',
219
- description: 'Database query returns all records without limit. Can exhaust memory on large tables.',
220
- fix: 'Add pagination: .find({}).limit(50).skip(offset)',
221
- },
222
- ];
223
-
224
- // OpenAPI/Swagger spec patterns
225
- const OPENAPI_PATTERNS = [
226
- {
227
- rule: 'OPENAPI_NO_SECURITY',
228
- title: 'OpenAPI: No Security Scheme Defined',
229
- regex: /(?:openapi|swagger)\s*:\s*["']?[23]\./g,
230
- severity: 'high',
231
- cwe: 'CWE-306',
232
- owasp: 'A07:2021',
233
- confidence: 'medium',
234
- description: 'OpenAPI spec detected without security schemes. API endpoints may be unprotected.',
235
- fix: 'Add securityDefinitions/securitySchemes to your OpenAPI spec',
236
- },
237
- {
238
- rule: 'OPENAPI_HTTP_SERVER',
239
- title: 'OpenAPI: Non-HTTPS Server URL',
240
- regex: /url\s*:\s*["']?http:\/\//g,
241
- severity: 'high',
242
- cwe: 'CWE-319',
243
- description: 'OpenAPI spec defines an HTTP (non-HTTPS) server URL. Use HTTPS in production.',
244
- fix: 'Change server URL to use https://',
245
- },
246
- {
247
- rule: 'OPENAPI_EXAMPLE_SECRETS',
248
- title: 'OpenAPI: Secrets in Examples',
249
- regex: /example\s*:\s*['"]?(?:sk-|sk_live_|ghp_|gho_|AKIA[0-9A-Z]|Bearer\s+ey[A-Za-z0-9])/g,
250
- severity: 'critical',
251
- cwe: 'CWE-798',
252
- description: 'Real API keys or tokens found in OpenAPI spec examples.',
253
- fix: 'Replace real secrets with placeholder values in examples',
254
- },
255
- ];
256
-
257
- export class APIFuzzer extends BaseAgent {
258
- constructor() {
259
- super('APIFuzzer', 'API endpoint security analysis', 'api');
260
- }
261
-
262
- async analyze(context) {
263
- const { files } = context;
264
- const codeFiles = files.filter(f => {
265
- const ext = path.extname(f).toLowerCase();
266
- return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.go'].includes(ext);
267
- });
268
-
269
- let findings = [];
270
- for (const file of codeFiles) {
271
- findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
272
- }
273
-
274
- // ── Project-level: Rate limiting check ────────────────────────────────────
275
- let hasExpressApp = false;
276
- let hasRateLimiter = false;
277
- let expressAppFile = null;
278
-
279
- for (const file of codeFiles) {
280
- const content = this.readFile(file);
281
- if (!content) continue;
282
- if (/(?:express\s*\(\)|app\.listen|createServer)\s*/.test(content)) {
283
- hasExpressApp = true;
284
- if (!expressAppFile) expressAppFile = file;
285
- }
286
- if (/(?:express-rate-limit|rate-limiter-flexible|@upstash\/ratelimit|limiter|bottleneck|rateLimit)/i.test(content)) {
287
- hasRateLimiter = true;
288
- }
289
- }
290
-
291
- if (hasExpressApp && !hasRateLimiter && expressAppFile) {
292
- findings.push(createFinding({
293
- file: expressAppFile,
294
- line: 0,
295
- severity: 'medium',
296
- category: 'api',
297
- rule: 'API_NO_RATE_LIMIT',
298
- title: 'API: No Rate Limiting Detected',
299
- description: 'HTTP server detected without any rate-limiting library. APIs without rate limits are vulnerable to brute-force and DoS attacks.',
300
- matched: 'No rate-limiting middleware found',
301
- confidence: 'medium',
302
- cwe: 'CWE-307',
303
- owasp: 'A07:2021',
304
- fix: 'Add rate limiting: npm i express-rate-limit && app.use(rateLimit({ windowMs: 15*60*1000, max: 100 }))',
305
- }));
306
- }
307
-
308
- // ── OpenAPI/Swagger spec scanning ─────────────────────────────────────────
309
- const specFiles = files.filter(f => {
310
- const basename = path.basename(f).toLowerCase();
311
- return /(?:openapi|swagger)\.(json|ya?ml)$/i.test(basename);
312
- });
313
-
314
- for (const file of specFiles) {
315
- const specFindings = this.scanFileWithPatterns(file, OPENAPI_PATTERNS);
316
- // Check if spec has securitySchemes
317
- const content = this.readFile(file);
318
- if (content && /(?:openapi|swagger)\s*:\s*["']?[23]\./.test(content)) {
319
- if (!/securitySchemes|securityDefinitions/i.test(content)) {
320
- findings.push(createFinding({
321
- file,
322
- line: 0,
323
- severity: 'high',
324
- category: 'api',
325
- rule: 'OPENAPI_NO_SECURITY',
326
- title: 'OpenAPI: No Security Scheme Defined',
327
- description: 'OpenAPI spec has no securitySchemes/securityDefinitions. API endpoints may be unprotected.',
328
- matched: 'Missing securitySchemes',
329
- confidence: 'high',
330
- cwe: 'CWE-306',
331
- fix: 'Add securitySchemes with Bearer token, API key, or OAuth2',
332
- }));
333
- }
334
- }
335
- // Add other OpenAPI pattern matches (HTTP server, example secrets)
336
- findings = findings.concat(
337
- specFindings.filter(f => f.rule !== 'OPENAPI_NO_SECURITY')
338
- );
339
- }
340
-
341
- return findings;
342
- }
343
- }
344
-
345
- export default APIFuzzer;
1
+ /**
2
+ * APIFuzzer Agent
3
+ * ================
4
+ *
5
+ * Static analysis of API endpoints for security anti-patterns.
6
+ * Checks authentication, authorization, input validation,
7
+ * rate limiting, CORS, error handling, data exposure,
8
+ * mass assignment, GraphQL, file uploads, and pagination.
9
+ */
10
+
11
+ import path from 'path';
12
+ import { BaseAgent, createFinding } from './base-agent.js';
13
+
14
+ const PATTERNS = [
15
+ // ── Missing Authentication ─────────────────────────────────────────────────
16
+ {
17
+ rule: 'API_NO_AUTH_CHECK',
18
+ title: 'API: Route Without Auth Check',
19
+ regex: /(?:app|router)\.(?:post|put|patch|delete)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:async\s+)?(?:\(req|function)/g,
20
+ severity: 'high',
21
+ cwe: 'CWE-306',
22
+ owasp: 'A07:2021',
23
+ confidence: 'medium',
24
+ description: 'State-changing API route without visible auth middleware. Verify authentication is enforced.',
25
+ fix: 'Add auth middleware: router.post("/api/data", authMiddleware, handler)',
26
+ },
27
+
28
+ // ── Input Validation ───────────────────────────────────────────────────────
29
+ {
30
+ rule: 'API_NO_VALIDATION',
31
+ title: 'API: No Input Validation',
32
+ regex: /(?:req\.body|request\.body|ctx\.request\.body)\s*(?:;|\))/g,
33
+ severity: 'medium',
34
+ cwe: 'CWE-20',
35
+ owasp: 'A03:2021',
36
+ confidence: 'low',
37
+ description: 'Request body used without validation. Validate with Zod, Joi, or Yup.',
38
+ fix: 'Validate: const data = schema.parse(req.body) using Zod or similar',
39
+ },
40
+ {
41
+ rule: 'API_SPREAD_BODY',
42
+ title: 'API: Spread Request Body into Operation',
43
+ regex: /\.\.\.\s*(?:req\.body|request\.body|ctx\.request\.body)/g,
44
+ severity: 'high',
45
+ cwe: 'CWE-915',
46
+ owasp: 'A01:2021',
47
+ description: 'Spreading request body enables mass assignment. Only use allowed fields.',
48
+ fix: 'Destructure specific fields: const { name, email } = req.body',
49
+ },
50
+
51
+ // ── Error Handling ─────────────────────────────────────────────────────────
52
+ {
53
+ rule: 'API_STACK_TRACE_RESPONSE',
54
+ title: 'API: Stack Trace in Response',
55
+ regex: /(?:res\.(?:json|send|status)|ctx\.body)\s*\(\s*(?:\{[^}]*(?:err\.stack|error\.stack|err\.message|error\.message)|err\b|error\b)/g,
56
+ severity: 'medium',
57
+ cwe: 'CWE-209',
58
+ owasp: 'A05:2021',
59
+ description: 'Error details sent in API response leak internal information.',
60
+ fix: 'Log errors server-side. Return generic message: res.status(500).json({ error: "Internal error" })',
61
+ },
62
+
63
+ // ── Data Exposure ──────────────────────────────────────────────────────────
64
+ {
65
+ rule: 'API_EXCESSIVE_DATA',
66
+ title: 'API: Returning Full Database Object',
67
+ regex: /(?:res\.json|res\.send|ctx\.body)\s*\(\s*(?:user|users|record|result|data|row|document)\s*\)/g,
68
+ severity: 'medium',
69
+ cwe: 'CWE-200',
70
+ owasp: 'A01:2021',
71
+ confidence: 'low',
72
+ description: 'Returning full DB objects may expose sensitive fields (password, email, etc.).',
73
+ fix: 'Select specific fields: res.json({ id: user.id, name: user.name })',
74
+ },
75
+
76
+ // ── File Upload ────────────────────────────────────────────────────────────
77
+ {
78
+ rule: 'API_UNRESTRICTED_UPLOAD',
79
+ title: 'API: Unrestricted File Upload',
80
+ regex: /(?:multer|formidable|busboy|multiparty)\s*\(/g,
81
+ severity: 'medium',
82
+ cwe: 'CWE-434',
83
+ owasp: 'A04:2021',
84
+ confidence: 'low',
85
+ description: 'File upload handler detected. Ensure file type validation, size limits, and secure storage.',
86
+ fix: 'Add: fileFilter, limits: { fileSize: 5*1024*1024 }, and validate MIME type',
87
+ },
88
+ {
89
+ rule: 'API_UPLOAD_NO_TYPE_CHECK',
90
+ title: 'API: File Upload Without Type Validation',
91
+ regex: /(?<!_)(?:originalname|filename)\s*(?:\)|;)/g,
92
+ severity: 'high',
93
+ cwe: 'CWE-434',
94
+ owasp: 'A04:2021',
95
+ confidence: 'low',
96
+ description: 'File upload using original filename without type validation.',
97
+ fix: 'Validate file extension and MIME type. Generate random filenames for storage.',
98
+ },
99
+ {
100
+ rule: 'API_PATH_IN_FILENAME',
101
+ title: 'API: Path Traversal in File Upload',
102
+ regex: /path\.join\s*\([^)]*(?:originalname|filename|req\.file|req\.body)/g,
103
+ severity: 'critical',
104
+ cwe: 'CWE-22',
105
+ owasp: 'A01:2021',
106
+ description: 'User-supplied filename in path construction enables directory traversal.',
107
+ fix: 'Generate random filename: crypto.randomUUID() + path.extname(file.originalname)',
108
+ },
109
+
110
+ // ── GraphQL Security ───────────────────────────────────────────────────────
111
+ {
112
+ rule: 'GRAPHQL_INTROSPECTION',
113
+ title: 'GraphQL: Introspection Enabled',
114
+ regex: /introspection\s*:\s*true/g,
115
+ severity: 'medium',
116
+ cwe: 'CWE-200',
117
+ owasp: 'A05:2021',
118
+ description: 'GraphQL introspection enabled. Exposes full schema to attackers in production.',
119
+ fix: 'Disable in production: introspection: process.env.NODE_ENV !== "production"',
120
+ },
121
+ {
122
+ rule: 'GRAPHQL_NO_DEPTH_LIMIT',
123
+ title: 'GraphQL: No Query Depth Limit',
124
+ regex: /(?:ApolloServer|GraphQLServer|createYoga|makeExecutableSchema)\s*\(/g,
125
+ severity: 'medium',
126
+ cwe: 'CWE-400',
127
+ confidence: 'low',
128
+ description: 'GraphQL server without query depth limiting. Enables deeply nested DoS queries.',
129
+ fix: 'Add depth limiting: graphql-depth-limit or @escape.tech/graphql-armor',
130
+ },
131
+ {
132
+ rule: 'GRAPHQL_NO_COST_ANALYSIS',
133
+ title: 'GraphQL: No Query Cost Analysis',
134
+ regex: /(?:typeDefs|schema)\s*[:=].*(?:Query|Mutation)\s*\{/g,
135
+ severity: 'low',
136
+ cwe: 'CWE-400',
137
+ confidence: 'low',
138
+ description: 'GraphQL schema without query cost analysis. Complex queries can cause DoS.',
139
+ fix: 'Add query complexity analysis: graphql-query-complexity or graphql-armor',
140
+ },
141
+
142
+ // ── API Versioning & Documentation ─────────────────────────────────────────
143
+ {
144
+ rule: 'API_DEBUG_ENDPOINT',
145
+ title: 'API: Debug/Test Endpoint in Code',
146
+ regex: /(?:app|router)\.(?:get|post|all)\s*\(\s*['"]\/(?:debug|test|admin|internal|_internal|healthcheck\/debug)/gi,
147
+ severity: 'high',
148
+ cwe: 'CWE-489',
149
+ owasp: 'A05:2021',
150
+ description: 'Debug/test/admin endpoint detected. Ensure it is not accessible in production.',
151
+ fix: 'Remove debug endpoints or protect with auth + environment check',
152
+ },
153
+
154
+ // ── Response Headers ───────────────────────────────────────────────────────
155
+ {
156
+ rule: 'API_NO_SECURITY_HEADERS',
157
+ title: 'API: Missing Security Headers (Helmet)',
158
+ regex: /app\.(?:use|listen)\s*\(/g,
159
+ severity: 'low',
160
+ cwe: 'CWE-693',
161
+ owasp: 'A05:2021',
162
+ confidence: 'low',
163
+ description: 'Express app without helmet middleware. Missing security headers (CSP, HSTS, etc.).',
164
+ fix: 'Add helmet: app.use(helmet()) for automatic security headers',
165
+ },
166
+
167
+ // ── Sensitive Data in URL ──────────────────────────────────────────────────
168
+ {
169
+ rule: 'API_KEY_IN_URL',
170
+ title: 'API: Secret in URL Query Parameter',
171
+ regex: /(?:url|endpoint|href)\s*[:=]\s*[`"'][^`"']*\?[^`"']*(?:key|token|secret|password|apiKey|api_key)\s*=/gi,
172
+ severity: 'high',
173
+ cwe: 'CWE-598',
174
+ owasp: 'A02:2021',
175
+ description: 'API key or secret passed in URL query parameter. URLs are logged in server logs, browser history, and proxies.',
176
+ fix: 'Move secrets to request headers (e.g., Authorization, x-api-key) instead of URL parameters.',
177
+ },
178
+ {
179
+ rule: 'API_SECRET_IN_URL',
180
+ title: 'API: Sensitive Data in URL Parameters',
181
+ regex: /(?:app|router)\.(?:get|post)\s*\(\s*['"][^'"]*(?::token|:apiKey|:password|:secret|:key)\b/g,
182
+ severity: 'high',
183
+ cwe: 'CWE-598',
184
+ owasp: 'A04:2021',
185
+ description: 'Sensitive data in URL parameters gets logged in server logs, browser history, and proxies.',
186
+ fix: 'Move sensitive data to request headers or body',
187
+ },
188
+
189
+ // ── Server Configuration ───────────────────────────────────────────────────
190
+ {
191
+ rule: 'API_TRUST_PROXY',
192
+ title: 'API: Trust Proxy Not Configured',
193
+ regex: /app\.set\s*\(\s*['"]trust proxy['"]\s*,\s*true\s*\)/g,
194
+ severity: 'low',
195
+ cwe: 'CWE-346',
196
+ confidence: 'low',
197
+ description: 'trust proxy set to true trusts all proxies. Specify trusted proxy IPs.',
198
+ fix: 'Set specific proxy: app.set("trust proxy", "loopback") or IP address',
199
+ },
200
+
201
+ // ── Denial of Service ──────────────────────────────────────────────────────
202
+ {
203
+ rule: 'API_LARGE_BODY_NO_LIMIT',
204
+ title: 'API: No Request Body Size Limit',
205
+ regex: /(?:express\.json|bodyParser\.json)\s*\(\s*\)/g,
206
+ severity: 'medium',
207
+ cwe: 'CWE-400',
208
+ confidence: 'low',
209
+ description: 'No body size limit configured. Large payloads can cause memory exhaustion.',
210
+ fix: 'Set limit: express.json({ limit: "1mb" })',
211
+ },
212
+ {
213
+ rule: 'API_NO_PAGINATION',
214
+ title: 'API: Query Without Limit',
215
+ regex: /\.find\s*\(\s*\{?\s*\}\s*\)/g,
216
+ severity: 'low',
217
+ cwe: 'CWE-400',
218
+ confidence: 'low',
219
+ description: 'Database query returns all records without limit. Can exhaust memory on large tables.',
220
+ fix: 'Add pagination: .find({}).limit(50).skip(offset)',
221
+ },
222
+ ];
223
+
224
+ // OpenAPI/Swagger spec patterns
225
+ const OPENAPI_PATTERNS = [
226
+ {
227
+ rule: 'OPENAPI_NO_SECURITY',
228
+ title: 'OpenAPI: No Security Scheme Defined',
229
+ regex: /(?:openapi|swagger)\s*:\s*["']?[23]\./g,
230
+ severity: 'high',
231
+ cwe: 'CWE-306',
232
+ owasp: 'A07:2021',
233
+ confidence: 'medium',
234
+ description: 'OpenAPI spec detected without security schemes. API endpoints may be unprotected.',
235
+ fix: 'Add securityDefinitions/securitySchemes to your OpenAPI spec',
236
+ },
237
+ {
238
+ rule: 'OPENAPI_HTTP_SERVER',
239
+ title: 'OpenAPI: Non-HTTPS Server URL',
240
+ regex: /url\s*:\s*["']?http:\/\//g,
241
+ severity: 'high',
242
+ cwe: 'CWE-319',
243
+ description: 'OpenAPI spec defines an HTTP (non-HTTPS) server URL. Use HTTPS in production.',
244
+ fix: 'Change server URL to use https://',
245
+ },
246
+ {
247
+ rule: 'OPENAPI_EXAMPLE_SECRETS',
248
+ title: 'OpenAPI: Secrets in Examples',
249
+ regex: /example\s*:\s*['"]?(?:sk-|sk_live_|ghp_|gho_|AKIA[0-9A-Z]|Bearer\s+ey[A-Za-z0-9])/g,
250
+ severity: 'critical',
251
+ cwe: 'CWE-798',
252
+ description: 'Real API keys or tokens found in OpenAPI spec examples.',
253
+ fix: 'Replace real secrets with placeholder values in examples',
254
+ },
255
+ ];
256
+
257
+ export class APIFuzzer extends BaseAgent {
258
+ constructor() {
259
+ super('APIFuzzer', 'API endpoint security analysis', 'api');
260
+ }
261
+
262
+ async analyze(context) {
263
+ const { files } = context;
264
+ const codeFiles = files.filter(f => {
265
+ const ext = path.extname(f).toLowerCase();
266
+ return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.go'].includes(ext);
267
+ });
268
+
269
+ let findings = [];
270
+ for (const file of codeFiles) {
271
+ findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
272
+ }
273
+
274
+ // ── Project-level: Rate limiting check ────────────────────────────────────
275
+ let hasExpressApp = false;
276
+ let hasRateLimiter = false;
277
+ let expressAppFile = null;
278
+
279
+ for (const file of codeFiles) {
280
+ const content = this.readFile(file);
281
+ if (!content) continue;
282
+ if (/(?:express\s*\(\)|app\.listen|createServer)\s*/.test(content)) {
283
+ hasExpressApp = true;
284
+ if (!expressAppFile) expressAppFile = file;
285
+ }
286
+ if (/(?:express-rate-limit|rate-limiter-flexible|@upstash\/ratelimit|limiter|bottleneck|rateLimit)/i.test(content)) {
287
+ hasRateLimiter = true;
288
+ }
289
+ }
290
+
291
+ if (hasExpressApp && !hasRateLimiter && expressAppFile) {
292
+ findings.push(createFinding({
293
+ file: expressAppFile,
294
+ line: 0,
295
+ severity: 'medium',
296
+ category: 'api',
297
+ rule: 'API_NO_RATE_LIMIT',
298
+ title: 'API: No Rate Limiting Detected',
299
+ description: 'HTTP server detected without any rate-limiting library. APIs without rate limits are vulnerable to brute-force and DoS attacks.',
300
+ matched: 'No rate-limiting middleware found',
301
+ confidence: 'medium',
302
+ cwe: 'CWE-307',
303
+ owasp: 'A07:2021',
304
+ fix: 'Add rate limiting: npm i express-rate-limit && app.use(rateLimit({ windowMs: 15*60*1000, max: 100 }))',
305
+ }));
306
+ }
307
+
308
+ // ── OpenAPI/Swagger spec scanning ─────────────────────────────────────────
309
+ const specFiles = files.filter(f => {
310
+ const basename = path.basename(f).toLowerCase();
311
+ return /(?:openapi|swagger)\.(json|ya?ml)$/i.test(basename);
312
+ });
313
+
314
+ for (const file of specFiles) {
315
+ const specFindings = this.scanFileWithPatterns(file, OPENAPI_PATTERNS);
316
+ // Check if spec has securitySchemes
317
+ const content = this.readFile(file);
318
+ if (content && /(?:openapi|swagger)\s*:\s*["']?[23]\./.test(content)) {
319
+ if (!/securitySchemes|securityDefinitions/i.test(content)) {
320
+ findings.push(createFinding({
321
+ file,
322
+ line: 0,
323
+ severity: 'high',
324
+ category: 'api',
325
+ rule: 'OPENAPI_NO_SECURITY',
326
+ title: 'OpenAPI: No Security Scheme Defined',
327
+ description: 'OpenAPI spec has no securitySchemes/securityDefinitions. API endpoints may be unprotected.',
328
+ matched: 'Missing securitySchemes',
329
+ confidence: 'high',
330
+ cwe: 'CWE-306',
331
+ fix: 'Add securitySchemes with Bearer token, API key, or OAuth2',
332
+ }));
333
+ }
334
+ }
335
+ // Add other OpenAPI pattern matches (HTTP server, example secrets)
336
+ findings = findings.concat(
337
+ specFindings.filter(f => f.rule !== 'OPENAPI_NO_SECURITY')
338
+ );
339
+ }
340
+
341
+ return findings;
342
+ }
343
+ }
344
+
345
+ export default APIFuzzer;