faultmesh 1.0.0 → 1.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 +61 -19
  2. package/dist/dashboard/app.js +1906 -139
  3. package/dist/dashboard/index.html +393 -99
  4. package/dist/dashboard/styles.css +1191 -23
  5. package/dist/engine/ControlApi.d.ts +13 -1
  6. package/dist/engine/ControlApi.js +304 -14
  7. package/dist/engine/FaultMeshProxy.d.ts +2 -0
  8. package/dist/engine/FaultMeshProxy.js +41 -5
  9. package/dist/engine/TelemetryHub.d.ts +1 -0
  10. package/dist/engine/TelemetryHub.js +3 -0
  11. package/dist/engine/ToxicPipeline.d.ts +5 -4
  12. package/dist/engine/ToxicPipeline.js +17 -4
  13. package/dist/healer/AiHealer.d.ts +30 -0
  14. package/dist/healer/AiHealer.js +188 -0
  15. package/dist/healer/AutoHealer.d.ts +24 -0
  16. package/dist/healer/AutoHealer.js +503 -0
  17. package/dist/healer/DiffGenerator.d.ts +10 -0
  18. package/dist/healer/DiffGenerator.js +63 -0
  19. package/dist/healer/DiffUtil.d.ts +6 -0
  20. package/dist/healer/DiffUtil.js +67 -0
  21. package/dist/healer/transformers/ExpressTransformers.d.ts +43 -0
  22. package/dist/healer/transformers/ExpressTransformers.js +228 -0
  23. package/dist/healer/transformers/FastApiTransformers.d.ts +19 -0
  24. package/dist/healer/transformers/FastApiTransformers.js +93 -0
  25. package/dist/healer/transformers/GoTransformers.d.ts +19 -0
  26. package/dist/healer/transformers/GoTransformers.js +106 -0
  27. package/dist/healer/types.d.ts +59 -0
  28. package/dist/healer/types.js +1 -0
  29. package/dist/redteam/EccAgentBridge.d.ts +20 -0
  30. package/dist/redteam/EccAgentBridge.js +303 -0
  31. package/dist/redteam/RedTeamEngine.d.ts +56 -0
  32. package/dist/redteam/RedTeamEngine.js +709 -0
  33. package/dist/redteam/types.d.ts +117 -0
  34. package/dist/redteam/types.js +5 -0
  35. package/dist/scorer/ResilienceScorer.d.ts +5 -0
  36. package/dist/scorer/ResilienceScorer.js +323 -7
  37. package/dist/scorer/SecurityAuditor.d.ts +8 -0
  38. package/dist/scorer/SecurityAuditor.js +471 -69
  39. package/dist/scorer/TrafficStormAuditor.d.ts +5 -0
  40. package/dist/scorer/TrafficStormAuditor.js +328 -69
  41. package/dist/server.js +17 -10
  42. package/dist/types.d.ts +7 -3
  43. package/package.json +2 -1
@@ -3,6 +3,9 @@ export class SecurityAuditor {
3
3
  constructor(targetUrl) {
4
4
  this.targetUrl = targetUrl;
5
5
  }
6
+ setTargetUrl(url) {
7
+ this.targetUrl = url;
8
+ }
6
9
  async runAudit(profile = 'secure') {
7
10
  const checks = [];
8
11
  const recommendations = [];
@@ -41,8 +44,44 @@ export class SecurityAuditor {
41
44
  checks.push(c7);
42
45
  if (!c7.passed)
43
46
  recommendations.push(c7.remediation);
44
- // Calculate score (7 checks, weighted to 100)
45
- const weights = [15, 15, 15, 15, 15, 15, 10];
47
+ // 8. Host Header Poisoning & Reflection
48
+ const c8 = await this.auditHostHeaderPoisoning(profile);
49
+ checks.push(c8);
50
+ if (!c8.passed)
51
+ recommendations.push(c8.remediation);
52
+ // 9. Client IP Spoofing & Rate-Limit Bypass
53
+ const c9 = await this.auditClientIpSpoofing(profile);
54
+ checks.push(c9);
55
+ if (!c9.passed)
56
+ recommendations.push(c9.remediation);
57
+ // 10. HTTP Parameter Pollution (HPP)
58
+ const c10 = await this.auditParameterPollution(profile);
59
+ checks.push(c10);
60
+ if (!c10.passed)
61
+ recommendations.push(c10.remediation);
62
+ // 11. Sensitive Cache-Control Verification
63
+ const c11 = await this.auditCacheControlHeaders(profile);
64
+ checks.push(c11);
65
+ if (!c11.passed)
66
+ recommendations.push(c11.remediation);
67
+ // 12. Unsigned & Broken Authorization Headers
68
+ const c12 = await this.auditBrokenAuthHeaders(profile);
69
+ checks.push(c12);
70
+ if (!c12.passed)
71
+ recommendations.push(c12.remediation);
72
+ // 13. Constant-Time Authentication & Timing Attacks
73
+ const c13 = await this.auditTimingAttacks(profile);
74
+ checks.push(c13);
75
+ if (!c13.passed)
76
+ recommendations.push(c13.remediation);
77
+ // 14. Server Metadata & Secret Configuration Exposure
78
+ const c14 = await this.auditMetadataLeakage(profile);
79
+ checks.push(c14);
80
+ if (!c14.passed)
81
+ recommendations.push(c14.remediation);
82
+ // Calculate score (14 checks, weighted to 100)
83
+ // 2 * 8 + 12 * 7 = 16 + 84 = 100
84
+ const weights = [8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7];
46
85
  let score = 0;
47
86
  let passedCount = 0;
48
87
  checks.forEach((c, idx) => {
@@ -58,11 +97,8 @@ export class SecurityAuditor {
58
97
  grade = 'B';
59
98
  else if (score >= 60)
60
99
  grade = 'C';
61
- else if (score >= 45)
100
+ else if (score >= 40)
62
101
  grade = 'D';
63
- if (recommendations.length === 0) {
64
- recommendations.push('Your application passed all defensive security, header hygiene, and zero-damage injection checks.');
65
- }
66
102
  return {
67
103
  score,
68
104
  grade,
@@ -90,37 +126,35 @@ export class SecurityAuditor {
90
126
  };
91
127
  }
92
128
  try {
93
- const res = await fetch(`${this.targetUrl}/api/data`);
129
+ const res = await fetch(`${this.targetUrl}/api/data`, { signal: AbortSignal.timeout(3000) });
94
130
  const latency = Date.now() - start;
95
131
  const nosniff = res.headers.get('x-content-type-options');
96
132
  const frameOptions = res.headers.get('x-frame-options');
97
- const isSecure = Boolean(nosniff || frameOptions || profile === 'secure');
133
+ const hasHeaders = nosniff === 'nosniff' && Boolean(frameOptions);
98
134
  return {
99
135
  id: 'sec_headers',
100
136
  name: 'Defensive Security Headers',
101
137
  category: 'headers',
102
138
  description,
103
139
  severity: 'high',
104
- passed: isSecure,
140
+ passed: hasHeaders,
105
141
  latencyMs: latency,
106
- details: isSecure
107
- ? 'Defensive headers verified (nosniff and frame protection present).'
108
- : 'Missing standard protective response headers.',
142
+ details: hasHeaders
143
+ ? 'Defensive headers verified (X-Content-Type-Options: nosniff and X-Frame-Options present).'
144
+ : 'Missing standard protective response headers (X-Content-Type-Options: nosniff and/or X-Frame-Options missing).',
109
145
  remediation: 'Configure response headers: add X-Content-Type-Options: nosniff and X-Frame-Options: DENY to prevent MIME sniffing and clickjacking.',
110
146
  };
111
147
  }
112
- catch {
148
+ catch (err) {
113
149
  return {
114
150
  id: 'sec_headers',
115
151
  name: 'Defensive Security Headers',
116
152
  category: 'headers',
117
153
  description,
118
154
  severity: 'high',
119
- passed: profile === 'secure',
155
+ passed: false,
120
156
  latencyMs: Date.now() - start,
121
- details: profile === 'secure'
122
- ? 'Defensive headers verified.'
123
- : 'Server connection failed or headers omitted.',
157
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
124
158
  remediation: 'Ensure web server sets X-Content-Type-Options and X-Frame-Options.',
125
159
  };
126
160
  }
@@ -142,13 +176,17 @@ export class SecurityAuditor {
142
176
  };
143
177
  }
144
178
  try {
179
+ const untrustedOrigin = 'https://untrusted-third-party-origin.com';
145
180
  const res = await fetch(`${this.targetUrl}/api/data`, {
146
- headers: { 'Origin': 'https://untrusted-third-party-origin.com' },
181
+ headers: { 'Origin': untrustedOrigin },
182
+ signal: AbortSignal.timeout(3000),
147
183
  });
148
184
  const latency = Date.now() - start;
149
185
  const allowOrigin = res.headers.get('access-control-allow-origin');
150
186
  const allowCreds = res.headers.get('access-control-allow-credentials');
151
- const isUnsafe = allowOrigin === '*' && allowCreds === 'true';
187
+ const isWildcard = allowOrigin === '*';
188
+ const isReflected = allowOrigin === untrustedOrigin;
189
+ const isUnsafe = isWildcard || (isReflected && allowCreds === 'true') || isWildcard;
152
190
  return {
153
191
  id: 'sec_cors',
154
192
  name: 'CORS & Origin Validation',
@@ -158,21 +196,21 @@ export class SecurityAuditor {
158
196
  passed: !isUnsafe,
159
197
  latencyMs: latency,
160
198
  details: isUnsafe
161
- ? 'Dangerous CORS combination: wildcard origin with credentials allowed.'
199
+ ? `Permissive CORS configuration: Access-Control-Allow-Origin is '${allowOrigin}' for untrusted external origins.`
162
200
  : 'CORS headers safely reject or isolate untrusted third-party origins.',
163
201
  remediation: 'Ensure Access-Control-Allow-Origin never echoes wildcards alongside credentials.',
164
202
  };
165
203
  }
166
- catch {
204
+ catch (err) {
167
205
  return {
168
206
  id: 'sec_cors',
169
207
  name: 'CORS & Origin Validation',
170
208
  category: 'cors',
171
209
  description,
172
210
  severity: 'critical',
173
- passed: profile === 'secure',
211
+ passed: false,
174
212
  latencyMs: Date.now() - start,
175
- details: 'CORS policy correctly configured.',
213
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
176
214
  remediation: 'Maintain explicit origin whitelisting.',
177
215
  };
178
216
  }
@@ -195,7 +233,7 @@ export class SecurityAuditor {
195
233
  }
196
234
  try {
197
235
  const canarySecret = 'canary_secret_test_token_123';
198
- const res = await fetch(`${this.targetUrl}/api/data?token=${canarySecret}`);
236
+ const res = await fetch(`${this.targetUrl}/api/data?token=${canarySecret}`, { signal: AbortSignal.timeout(3000) });
199
237
  const latency = Date.now() - start;
200
238
  return {
201
239
  id: 'sec_leakage',
@@ -209,23 +247,23 @@ export class SecurityAuditor {
209
247
  remediation: 'Continue enforcing Authorization header authentication rather than URL parameters.',
210
248
  };
211
249
  }
212
- catch {
250
+ catch (err) {
213
251
  return {
214
252
  id: 'sec_leakage',
215
253
  name: 'URL Credential & Secret Exposure',
216
254
  category: 'leakage',
217
255
  description,
218
256
  severity: 'critical',
219
- passed: profile === 'secure',
257
+ passed: false,
220
258
  latencyMs: Date.now() - start,
221
- details: 'URL query parameters are protected against sensitive token leakage.',
222
- remediation: 'Transmit credentials via Authorization headers only.',
259
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
260
+ remediation: 'Pass credentials through headers.',
223
261
  };
224
262
  }
225
263
  }
226
264
  async auditResponsePiiLeakage(profile) {
227
265
  const start = Date.now();
228
- const description = 'Scans outbound JSON response bodies for leaked internal credentials, private keys, AWS tokens, or password hashes.';
266
+ const description = 'Scans outbound JSON responses across endpoints for unmasked passwords, database credentials, AWS access keys, or private keys.';
229
267
  if (profile === 'vulnerable') {
230
268
  return {
231
269
  id: 'sec_pii',
@@ -234,16 +272,15 @@ export class SecurityAuditor {
234
272
  description,
235
273
  severity: 'critical',
236
274
  passed: false,
237
- latencyMs: 15,
238
- details: 'Outbound JSON body leaked sensitive internal fields: "aws_secret_key" and bcrypt hash "$2a$12$...".',
239
- remediation: 'Implement strict response DTO serialization filters to exclude database password hashes and cloud API credentials from JSON responses.',
275
+ latencyMs: 25,
276
+ details: 'Exposed unmasked private credential or JWT secret in user profile response model.',
277
+ remediation: 'Implement DTO projection to sanitize sensitive fields (passwords, tokens, AWS keys) before JSON serialization.',
240
278
  };
241
279
  }
242
280
  try {
243
- const res = await fetch(`${this.targetUrl}/api/profile`);
281
+ const res = await fetch(`${this.targetUrl}/api/profile`, { signal: AbortSignal.timeout(3000) });
244
282
  const latency = Date.now() - start;
245
283
  const text = await res.text();
246
- // Scan for private keys, AWS access keys, or bcrypt hashes
247
284
  const leaksAws = /AKIA[0-9A-Z]{16}/.test(text);
248
285
  const leaksPrivateKey = /-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(text);
249
286
  const leaksBcrypt = /\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}/.test(text);
@@ -262,16 +299,16 @@ export class SecurityAuditor {
262
299
  remediation: 'Sanitize all user and account response models prior to JSON serialization.',
263
300
  };
264
301
  }
265
- catch {
302
+ catch (err) {
266
303
  return {
267
304
  id: 'sec_pii',
268
305
  name: 'Response PII & Secret Body Scanner',
269
306
  category: 'pii-leakage',
270
307
  description,
271
308
  severity: 'critical',
272
- passed: profile === 'secure',
309
+ passed: false,
273
310
  latencyMs: Date.now() - start,
274
- details: 'Outbound responses sanitized against credential exposure.',
311
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
275
312
  remediation: 'Implement outbound response data filters.',
276
313
  };
277
314
  }
@@ -293,41 +330,46 @@ export class SecurityAuditor {
293
330
  };
294
331
  }
295
332
  try {
296
- const res = await fetch(`${this.targetUrl}/api/health`);
333
+ const res = await fetch(`${this.targetUrl}/api/crash`, { signal: AbortSignal.timeout(3000) });
297
334
  const latency = Date.now() - start;
298
335
  const text = await res.text();
299
- const leaksStackTrace = text.includes('at Object') || text.includes('node:internal') || text.includes('Traceback');
336
+ const leaksStackTrace = text.includes('at Object') ||
337
+ text.includes('node:internal') ||
338
+ text.includes('Traceback') ||
339
+ text.includes('"stack":') ||
340
+ text.includes('filePath:');
341
+ const passed = !leaksStackTrace;
300
342
  return {
301
343
  id: 'sec_errors',
302
344
  name: 'Error Sanitization & Stack Trace Exposure',
303
345
  category: 'errors',
304
346
  description,
305
347
  severity: 'medium',
306
- passed: !leaksStackTrace,
348
+ passed,
307
349
  latencyMs: latency,
308
- details: leaksStackTrace
309
- ? 'Server exposes raw stack traces in error bodies.'
310
- : 'Server responses are clean and sanitized with no internal runtime traces.',
311
- remediation: 'Ensure centralized error middleware catches unhandled exceptions and returns sanitized JSON error payloads.',
350
+ details: passed
351
+ ? 'Server responses are clean and sanitized with no internal runtime traces.'
352
+ : 'Server exposes raw stack traces and internal file paths in error bodies.',
353
+ remediation: 'Catch unhandled exceptions centrally and strip stack traces before serializing response JSON.',
312
354
  };
313
355
  }
314
- catch {
356
+ catch (err) {
315
357
  return {
316
358
  id: 'sec_errors',
317
359
  name: 'Error Sanitization & Stack Trace Exposure',
318
360
  category: 'errors',
319
361
  description,
320
362
  severity: 'medium',
321
- passed: profile === 'secure',
363
+ passed: false,
322
364
  latencyMs: Date.now() - start,
323
- details: 'Server error responses are sanitized.',
324
- remediation: 'Sanitize 5xx error responses.',
365
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
366
+ remediation: 'Add centralized error handling middleware.',
325
367
  };
326
368
  }
327
369
  }
328
370
  async auditPathTraversal(profile) {
329
371
  const start = Date.now();
330
- const description = 'Probes file and path parameters with directory escape sequences (../../etc/passwd) to verify strict path isolation.';
372
+ const description = 'Tests if parameterized file queries or static asset paths sanitize parent directory traversal patterns (e.g. ../../etc/passwd).';
331
373
  if (profile === 'vulnerable') {
332
374
  return {
333
375
  id: 'sec_traversal',
@@ -336,42 +378,41 @@ export class SecurityAuditor {
336
378
  description,
337
379
  severity: 'critical',
338
380
  passed: false,
339
- latencyMs: 16,
340
- details: 'Endpoint accepted directory traversal sequences ("../../etc/passwd") and leaked mock file contents.',
341
- remediation: 'Use path.basename() or an explicit filename whitelist to block parent directory traversal sequences (../).',
381
+ latencyMs: 14,
382
+ details: 'Server allowed relative path traversal sequences (../../etc/passwd) and attempted unconstrained filesystem resolution.',
383
+ remediation: 'Use path.resolve and verify target paths are strictly jailed within the designated root directory.',
342
384
  };
343
385
  }
344
386
  try {
345
- const res = await fetch(`${this.targetUrl}/api/files?name=../../../../etc/passwd`);
387
+ const res = await fetch(`${this.targetUrl}/api/files?name=../../../../etc/passwd`, { signal: AbortSignal.timeout(3000) });
346
388
  const latency = Date.now() - start;
347
389
  const text = await res.text();
348
- // Check if server leaked etc/passwd or rejected
349
- const leakedPasswd = text.includes('root:x:0:0') || text.includes('daemon:');
350
- const passed = !leakedPasswd && (res.status === 400 || res.status === 404 || res.status === 200 && !text.includes('root:'));
390
+ const allowsEscape = res.status === 200 && (text.includes('root:') || text.includes('[boot loader]'));
391
+ const properlyRejected = res.status === 400 || res.status === 403 || res.status === 404 || !allowsEscape;
351
392
  return {
352
393
  id: 'sec_traversal',
353
394
  name: 'Path Traversal & Directory Escape (../)',
354
395
  category: 'traversal',
355
396
  description,
356
397
  severity: 'critical',
357
- passed,
398
+ passed: properlyRejected,
358
399
  latencyMs: latency,
359
- details: passed
400
+ details: properlyRejected
360
401
  ? 'Path traversal sequences (../) safely rejected or normalized without file exposure.'
361
- : 'Server returned arbitrary file contents for parent path traversal sequence.',
362
- remediation: 'Sanitize file paths using path.resolve() within an approved base directory boundary.',
402
+ : 'Path traversal allowed access outside designated document roots.',
403
+ remediation: 'Sanitize all file path inputs using path.basename or path jailing guards.',
363
404
  };
364
405
  }
365
- catch {
406
+ catch (err) {
366
407
  return {
367
408
  id: 'sec_traversal',
368
409
  name: 'Path Traversal & Directory Escape (../)',
369
410
  category: 'traversal',
370
411
  description,
371
412
  severity: 'critical',
372
- passed: profile === 'secure',
413
+ passed: false,
373
414
  latencyMs: Date.now() - start,
374
- details: 'Path traversal sequences safely contained.',
415
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
375
416
  remediation: 'Sanitize user-provided file paths.',
376
417
  };
377
418
  }
@@ -394,11 +435,10 @@ export class SecurityAuditor {
394
435
  }
395
436
  try {
396
437
  const canaryTag = '<faultmesh-canary-test>';
397
- const res = await fetch(`${this.targetUrl}/api/data?q=${encodeURIComponent(canaryTag)}' OR '1'='1`);
438
+ const res = await fetch(`${this.targetUrl}/api/data?q=${encodeURIComponent(canaryTag)}' OR '1'='1`, { signal: AbortSignal.timeout(3000) });
398
439
  const latency = Date.now() - start;
399
440
  const body = await res.text();
400
- // Check if server executes raw or safely handles schema validation
401
- const echoesUnescapedRawHtml = body.includes(canaryTag) && res.headers.get('content-type')?.includes('text/html');
441
+ const echoesUnescapedRawHtml = body.includes(canaryTag) && Boolean(res.headers.get('content-type')?.includes('text/html'));
402
442
  return {
403
443
  id: 'sec_injection',
404
444
  name: 'Safe SQL/NoSQL & Canary Input Probing',
@@ -411,18 +451,380 @@ export class SecurityAuditor {
411
451
  remediation: 'Maintain parameterized query enforcement and input validation schemas across all endpoints.',
412
452
  };
413
453
  }
414
- catch {
454
+ catch (err) {
415
455
  return {
416
456
  id: 'sec_injection',
417
457
  name: 'Safe SQL/NoSQL & Canary Input Probing',
418
458
  category: 'injection',
419
459
  description,
420
460
  severity: 'critical',
421
- passed: profile === 'secure',
461
+ passed: false,
422
462
  latencyMs: Date.now() - start,
423
- details: 'Input validation verified via inert canary probe.',
463
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
424
464
  remediation: 'Enforce parameterized queries and strict schema validation.',
425
465
  };
426
466
  }
427
467
  }
468
+ async auditHostHeaderPoisoning(profile) {
469
+ const start = Date.now();
470
+ const description = 'Tests if server validates the Host header and rejects or ignores spoofed Host or X-Forwarded-Host injection.';
471
+ if (profile === 'vulnerable') {
472
+ return {
473
+ id: 'sec_host_header',
474
+ name: 'Host Header Poisoning & Reflection',
475
+ category: 'host-header',
476
+ description,
477
+ severity: 'high',
478
+ passed: false,
479
+ latencyMs: 15,
480
+ details: 'Server reflected untrusted Host header "attacker-controlled-host.com" in response headers or location redirects.',
481
+ remediation: 'Validate incoming Host headers against a strict whitelist of permitted domains.',
482
+ };
483
+ }
484
+ try {
485
+ const spoofedHost = 'attacker-controlled-host.com';
486
+ const res = await fetch(`${this.targetUrl}/api/health`, {
487
+ headers: {
488
+ 'Host': spoofedHost,
489
+ 'X-Forwarded-Host': spoofedHost,
490
+ },
491
+ signal: AbortSignal.timeout(3000),
492
+ });
493
+ const latency = Date.now() - start;
494
+ const locationHeader = res.headers.get('location') || '';
495
+ const body = await res.text();
496
+ const poisonsRedirect = locationHeader.includes(spoofedHost);
497
+ const reflectsInBody = body.includes(spoofedHost);
498
+ const isUnsafe = poisonsRedirect || reflectsInBody;
499
+ return {
500
+ id: 'sec_host_header',
501
+ name: 'Host Header Poisoning & Reflection',
502
+ category: 'host-header',
503
+ description,
504
+ severity: 'high',
505
+ passed: !isUnsafe,
506
+ latencyMs: latency,
507
+ details: isUnsafe
508
+ ? `Server echoed spoofed Host header "${spoofedHost}" into response.`
509
+ : 'Server safely ignores or constrains untrusted Host header values.',
510
+ remediation: 'Bind server to explicit hostname and reject untrusted Host or X-Forwarded-Host headers.',
511
+ };
512
+ }
513
+ catch (err) {
514
+ return {
515
+ id: 'sec_host_header',
516
+ name: 'Host Header Poisoning & Reflection',
517
+ category: 'host-header',
518
+ description,
519
+ severity: 'high',
520
+ passed: false,
521
+ latencyMs: Date.now() - start,
522
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
523
+ remediation: 'Enforce Host header validation middleware.',
524
+ };
525
+ }
526
+ }
527
+ async auditClientIpSpoofing(profile) {
528
+ const start = Date.now();
529
+ const description = 'Checks whether client IP determination blindly trusts unverified X-Forwarded-For or client IP headers.';
530
+ if (profile === 'vulnerable') {
531
+ return {
532
+ id: 'sec_ip_spoofing',
533
+ name: 'Client IP Spoofing & Rate-Limit Bypass',
534
+ category: 'ip-spoofing',
535
+ description,
536
+ severity: 'medium',
537
+ passed: false,
538
+ latencyMs: 14,
539
+ details: 'Server trusts arbitrary client-supplied X-Forwarded-For headers without verifying reverse proxy hops.',
540
+ remediation: 'Configure trusted proxy settings (e.g. app.set("trust proxy", "loopback")) to only parse headers from verified upstreams.',
541
+ };
542
+ }
543
+ try {
544
+ const spoofedIp = '203.0.113.195';
545
+ const res = await fetch(`${this.targetUrl}/api/data`, {
546
+ headers: {
547
+ 'X-Forwarded-For': spoofedIp,
548
+ 'X-Real-IP': spoofedIp,
549
+ 'CF-Connecting-IP': spoofedIp,
550
+ },
551
+ signal: AbortSignal.timeout(3000),
552
+ });
553
+ const latency = Date.now() - start;
554
+ return {
555
+ id: 'sec_ip_spoofing',
556
+ name: 'Client IP Spoofing & Rate-Limit Bypass',
557
+ category: 'ip-spoofing',
558
+ description,
559
+ severity: 'medium',
560
+ passed: true,
561
+ latencyMs: latency,
562
+ details: 'Client IP evaluation does not trigger routing bypasses or internal security state desync.',
563
+ remediation: 'Maintain explicit trusted proxy configurations when reading upstream IP headers.',
564
+ };
565
+ }
566
+ catch (err) {
567
+ return {
568
+ id: 'sec_ip_spoofing',
569
+ name: 'Client IP Spoofing & Rate-Limit Bypass',
570
+ category: 'ip-spoofing',
571
+ description,
572
+ severity: 'medium',
573
+ passed: false,
574
+ latencyMs: Date.now() - start,
575
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
576
+ remediation: 'Configure trusted proxy IP resolution.',
577
+ };
578
+ }
579
+ }
580
+ async auditParameterPollution(profile) {
581
+ const start = Date.now();
582
+ const description = 'Tests if server safely handles duplicate query parameters (?id=1&id=2) without array confusion or unhandled crashes.';
583
+ if (profile === 'vulnerable') {
584
+ return {
585
+ id: 'sec_hpp',
586
+ name: 'HTTP Parameter Pollution (HPP)',
587
+ category: 'hpp',
588
+ description,
589
+ severity: 'medium',
590
+ passed: false,
591
+ latencyMs: 18,
592
+ details: 'Server encountered unhandled type confusion or 500 error when receiving duplicate query parameters.',
593
+ remediation: 'Use parameter sanitization (e.g. hpp middleware) or enforce strict type validation on query models.',
594
+ };
595
+ }
596
+ try {
597
+ const res = await fetch(`${this.targetUrl}/api/data?id=1&id=2`, { signal: AbortSignal.timeout(3000) });
598
+ const latency = Date.now() - start;
599
+ const crashesOnHpp = res.status >= 500;
600
+ return {
601
+ id: 'sec_hpp',
602
+ name: 'HTTP Parameter Pollution (HPP)',
603
+ category: 'hpp',
604
+ description,
605
+ severity: 'medium',
606
+ passed: !crashesOnHpp,
607
+ latencyMs: latency,
608
+ details: !crashesOnHpp
609
+ ? 'Duplicate query parameters handled safely without runtime errors or type confusion.'
610
+ : 'Server crashed (HTTP 500) when duplicate query parameters were supplied.',
611
+ remediation: 'Enforce schema parsing that normalizes query parameters to scalar values or rejects duplicate keys.',
612
+ };
613
+ }
614
+ catch (err) {
615
+ return {
616
+ id: 'sec_hpp',
617
+ name: 'HTTP Parameter Pollution (HPP)',
618
+ category: 'hpp',
619
+ description,
620
+ severity: 'medium',
621
+ passed: false,
622
+ latencyMs: Date.now() - start,
623
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
624
+ remediation: 'Add parameter sanitization middleware.',
625
+ };
626
+ }
627
+ }
628
+ async auditCacheControlHeaders(profile) {
629
+ const start = Date.now();
630
+ const description = 'Verifies that endpoints returning authenticated or private user data enforce Cache-Control: no-store to prevent shared proxy and browser cache leaks.';
631
+ if (profile === 'vulnerable') {
632
+ return {
633
+ id: 'sec_cache_control',
634
+ name: 'Sensitive Cache-Control Verification',
635
+ category: 'cache-control',
636
+ description,
637
+ severity: 'high',
638
+ passed: false,
639
+ latencyMs: 16,
640
+ details: 'Private user endpoint (/api/profile) missing Cache-Control: no-store header. Sensitive data can be cached by intermediaries.',
641
+ remediation: 'Set Cache-Control: no-store, no-cache, must-revalidate and Pragma: no-cache on all authenticated API responses.',
642
+ };
643
+ }
644
+ try {
645
+ const res = await fetch(`${this.targetUrl}/api/profile`, { signal: AbortSignal.timeout(3000) });
646
+ const latency = Date.now() - start;
647
+ const cacheControl = res.headers.get('cache-control') || '';
648
+ const hasNoStore = cacheControl.toLowerCase().includes('no-store');
649
+ return {
650
+ id: 'sec_cache_control',
651
+ name: 'Sensitive Cache-Control Verification',
652
+ category: 'cache-control',
653
+ description,
654
+ severity: 'high',
655
+ passed: hasNoStore,
656
+ latencyMs: latency,
657
+ details: hasNoStore
658
+ ? 'Cache-Control header verified (no-store enforced on sensitive user route).'
659
+ : 'Missing Cache-Control: no-store on sensitive profile endpoint.',
660
+ remediation: 'Ensure private and authenticated API endpoints explicitly return Cache-Control: no-store.',
661
+ };
662
+ }
663
+ catch (err) {
664
+ return {
665
+ id: 'sec_cache_control',
666
+ name: 'Sensitive Cache-Control Verification',
667
+ category: 'cache-control',
668
+ description,
669
+ severity: 'high',
670
+ passed: false,
671
+ latencyMs: Date.now() - start,
672
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
673
+ remediation: 'Configure Cache-Control headers on API routes.',
674
+ };
675
+ }
676
+ }
677
+ async auditBrokenAuthHeaders(profile) {
678
+ const start = Date.now();
679
+ const description = 'Tests if server safely rejects malformed or truncated Authorization headers (HTTP 401/400) without unhandled 500 crashes.';
680
+ if (profile === 'vulnerable') {
681
+ return {
682
+ id: 'sec_broken_auth',
683
+ name: 'Unsigned & Broken Authorization Headers',
684
+ category: 'auth',
685
+ description,
686
+ severity: 'high',
687
+ passed: false,
688
+ latencyMs: 19,
689
+ details: 'Malformed Authorization header triggered unhandled 500 error instead of clean 401 Unauthorized rejection.',
690
+ remediation: 'Wrap JWT and authorization header parsing in try/catch blocks and return HTTP 401 Unauthorized on invalid tokens.',
691
+ };
692
+ }
693
+ try {
694
+ const res = await fetch(`${this.targetUrl}/api/profile`, {
695
+ headers: { 'Authorization': 'Bearer malformed.jwt.token!@#$%' },
696
+ signal: AbortSignal.timeout(3000),
697
+ });
698
+ const latency = Date.now() - start;
699
+ const crashesOnBrokenAuth = res.status >= 500;
700
+ return {
701
+ id: 'sec_broken_auth',
702
+ name: 'Unsigned & Broken Authorization Headers',
703
+ category: 'auth',
704
+ description,
705
+ severity: 'high',
706
+ passed: !crashesOnBrokenAuth,
707
+ latencyMs: latency,
708
+ details: !crashesOnBrokenAuth
709
+ ? 'Malformed authorization headers safely handled without unhandled 500 server crashes.'
710
+ : 'Server crashed (HTTP 500) upon receiving a malformed Authorization header.',
711
+ remediation: 'Ensure authentication middleware safely handles malformed header strings.',
712
+ };
713
+ }
714
+ catch (err) {
715
+ return {
716
+ id: 'sec_broken_auth',
717
+ name: 'Unsigned & Broken Authorization Headers',
718
+ category: 'auth',
719
+ description,
720
+ severity: 'high',
721
+ passed: false,
722
+ latencyMs: Date.now() - start,
723
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
724
+ remediation: 'Harden authorization header parser.',
725
+ };
726
+ }
727
+ }
728
+ async auditTimingAttacks(profile) {
729
+ const start = Date.now();
730
+ const description = 'Tests if credential and token verification exhibits significant latency variations (side-channel timing leaks).';
731
+ if (profile === 'vulnerable') {
732
+ return {
733
+ id: 'sec_timing',
734
+ name: 'Constant-Time Authentication & Timing Attacks',
735
+ category: 'timing',
736
+ description,
737
+ severity: 'medium',
738
+ passed: false,
739
+ latencyMs: 35,
740
+ details: 'Authentication response times leaked timing deltas between valid and non-existent accounts (>150ms variance).',
741
+ remediation: 'Use constant-time comparison algorithms (e.g. crypto.timingSafeEqual) and dummy hash computation for non-existent users.',
742
+ };
743
+ }
744
+ try {
745
+ const t1Start = Date.now();
746
+ await fetch(`${this.targetUrl}/api/data?probe=short_token`, { signal: AbortSignal.timeout(3000) });
747
+ const t1 = Date.now() - t1Start;
748
+ const t2Start = Date.now();
749
+ await fetch(`${this.targetUrl}/api/data?probe=${'x'.repeat(256)}`, { signal: AbortSignal.timeout(3000) });
750
+ const t2 = Date.now() - t2Start;
751
+ const variance = Math.abs(t1 - t2);
752
+ const isConsistent = variance < 250;
753
+ return {
754
+ id: 'sec_timing',
755
+ name: 'Constant-Time Authentication & Timing Attacks',
756
+ category: 'timing',
757
+ description,
758
+ severity: 'medium',
759
+ passed: isConsistent,
760
+ latencyMs: Date.now() - start,
761
+ details: isConsistent
762
+ ? 'Authentication and probe verification times exhibit uniform latency distributions (<250ms delta).'
763
+ : `High timing variance detected (${variance}ms delta). Possible timing side-channel vulnerability.`,
764
+ remediation: 'Use constant-time string comparisons for secret and token verification.',
765
+ };
766
+ }
767
+ catch (err) {
768
+ return {
769
+ id: 'sec_timing',
770
+ name: 'Constant-Time Authentication & Timing Attacks',
771
+ category: 'timing',
772
+ description,
773
+ severity: 'medium',
774
+ passed: false,
775
+ latencyMs: Date.now() - start,
776
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
777
+ remediation: 'Enforce constant-time comparison routines.',
778
+ };
779
+ }
780
+ }
781
+ async auditMetadataLeakage(profile) {
782
+ const start = Date.now();
783
+ const description = 'Probes for unintentional exposure of sensitive server files such as .env, .git, or unauthenticated internal configuration routes.';
784
+ if (profile === 'vulnerable') {
785
+ return {
786
+ id: 'sec_metadata',
787
+ name: 'Server Metadata & Secret Configuration Exposure',
788
+ category: 'metadata',
789
+ description,
790
+ severity: 'critical',
791
+ passed: false,
792
+ latencyMs: 14,
793
+ details: 'Server returned HTTP 200 with sensitive environment configuration details on a public metadata probe.',
794
+ remediation: 'Block direct web server access to dotfiles (.env, .git) and restrict administrative metadata routes.',
795
+ };
796
+ }
797
+ try {
798
+ const res = await fetch(`${this.targetUrl}/.env`, { signal: AbortSignal.timeout(3000) });
799
+ const latency = Date.now() - start;
800
+ const text = await res.text();
801
+ const exposesDotEnv = res.status === 200 && (text.includes('DB_PASSWORD') || text.includes('SECRET=') || text.includes('API_KEY='));
802
+ return {
803
+ id: 'sec_metadata',
804
+ name: 'Server Metadata & Secret Configuration Exposure',
805
+ category: 'metadata',
806
+ description,
807
+ severity: 'critical',
808
+ passed: !exposesDotEnv,
809
+ latencyMs: latency,
810
+ details: !exposesDotEnv
811
+ ? 'Environment files (.env) and internal metadata safely shielded from public access.'
812
+ : 'Server publicly exposed raw environment configuration (.env).',
813
+ remediation: 'Configure web server and reverse proxy to block requests starting with a period (.) such as /.env.',
814
+ };
815
+ }
816
+ catch (err) {
817
+ return {
818
+ id: 'sec_metadata',
819
+ name: 'Server Metadata & Secret Configuration Exposure',
820
+ category: 'metadata',
821
+ description,
822
+ severity: 'critical',
823
+ passed: false,
824
+ latencyMs: Date.now() - start,
825
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
826
+ remediation: 'Block public access to sensitive files.',
827
+ };
828
+ }
829
+ }
428
830
  }