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
@@ -2,9 +2,14 @@ import { TrafficStormScorecard } from '../types.js';
2
2
  export declare class TrafficStormAuditor {
3
3
  private targetUrl;
4
4
  constructor(targetUrl: string);
5
+ setTargetUrl(url: string): void;
5
6
  runStormSuite(profile?: 'resilient' | 'fragile'): Promise<TrafficStormScorecard>;
6
7
  private auditRateLimitBackoff;
7
8
  private auditOversizedPayload;
8
9
  private auditSlowlorisDefense;
9
10
  private auditIdempotencyProtection;
11
+ private auditReDosLockup;
12
+ private auditConcurrentRaceCondition;
13
+ private auditChunkedRequestDrip;
14
+ private auditResourceAvalancheFlood;
10
15
  }
@@ -3,10 +3,13 @@ export class TrafficStormAuditor {
3
3
  constructor(targetUrl) {
4
4
  this.targetUrl = targetUrl;
5
5
  }
6
+ setTargetUrl(url) {
7
+ this.targetUrl = url;
8
+ }
6
9
  async runStormSuite(profile = 'resilient') {
7
10
  const checks = [];
8
11
  const recommendations = [];
9
- // 1. Rate Limit Back-off & Retry Storms (HTTP 429)
12
+ // 1. Rate Limit Back-off & Retry Storm Handling
10
13
  const c1 = await this.auditRateLimitBackoff(profile);
11
14
  checks.push(c1);
12
15
  if (!c1.passed)
@@ -21,13 +24,34 @@ export class TrafficStormAuditor {
21
24
  checks.push(c3);
22
25
  if (!c3.passed)
23
26
  recommendations.push(c3.remediation);
24
- // 4. Duplicate Request & Idempotency Key Deduplication
27
+ // 4. Duplicate Request Idempotency Protection
25
28
  const c4 = await this.auditIdempotencyProtection(profile);
26
29
  checks.push(c4);
27
30
  if (!c4.passed)
28
31
  recommendations.push(c4.remediation);
29
- // Weighted scoring (4 checks: 25 points each = 100)
30
- const weights = [25, 25, 25, 25];
32
+ // 5. ReDoS (Regex Denial of Service) Lockup Probe
33
+ const c5 = await this.auditReDosLockup(profile);
34
+ checks.push(c5);
35
+ if (!c5.passed)
36
+ recommendations.push(c5.remediation);
37
+ // 6. Concurrent Race Condition & Double Processing
38
+ const c6 = await this.auditConcurrentRaceCondition(profile);
39
+ checks.push(c6);
40
+ if (!c6.passed)
41
+ recommendations.push(c6.remediation);
42
+ // 7. Chunked Request Drip & Slow POST Defense
43
+ const c7 = await this.auditChunkedRequestDrip(profile);
44
+ checks.push(c7);
45
+ if (!c7.passed)
46
+ recommendations.push(c7.remediation);
47
+ // 8. Resource Avalanche & Sudden Concurrency Flood
48
+ const c8 = await this.auditResourceAvalancheFlood(profile);
49
+ checks.push(c8);
50
+ if (!c8.passed)
51
+ recommendations.push(c8.remediation);
52
+ // Calculate score (8 checks, weighted to 100)
53
+ // 4 * 15 + 4 * 10 = 60 + 40 = 100
54
+ const weights = [15, 15, 15, 15, 10, 10, 10, 10];
31
55
  let score = 0;
32
56
  let passedCount = 0;
33
57
  checks.forEach((c, idx) => {
@@ -43,11 +67,8 @@ export class TrafficStormAuditor {
43
67
  grade = 'B';
44
68
  else if (score >= 60)
45
69
  grade = 'C';
46
- else if (score >= 45)
70
+ else if (score >= 40)
47
71
  grade = 'D';
48
- if (recommendations.length === 0) {
49
- recommendations.push('Your application safely handled all traffic storm, rate limit, and denial-of-service resilience tests.');
50
- }
51
72
  return {
52
73
  score,
53
74
  grade,
@@ -70,42 +91,46 @@ export class TrafficStormAuditor {
70
91
  severity: 'high',
71
92
  passed: false,
72
93
  latencyMs: 45,
73
- details: 'Client ignored HTTP 429 Retry-After header and triggered 12 rapid retries within 300ms, causing an unmitigated retry storm.',
94
+ details: 'Client ignored HTTP 429 Retry-After header and triggered 12 rapid retries within 300ms, causing an unmitigated retry stampede.',
74
95
  remediation: 'Implement exponential backoff with jitter and respect the standard Retry-After header when receiving HTTP 429.',
75
96
  };
76
97
  }
77
98
  try {
78
- const res = await fetch(`${this.targetUrl}/api/ratelimit-test`);
99
+ const res = await fetch(`${this.targetUrl}/api/ratelimit-test`, { signal: AbortSignal.timeout(3000) });
79
100
  const latency = Date.now() - start;
101
+ const retryAfter = res.headers.get('retry-after');
102
+ const isRateLimited = res.status === 429 && Boolean(retryAfter);
80
103
  return {
81
104
  id: 'storm_ratelimit',
82
105
  name: 'Rate Limit Back-off & Retry Storm Handling',
83
106
  category: 'ratelimit',
84
107
  description,
85
108
  severity: 'high',
86
- passed: true,
109
+ passed: isRateLimited,
87
110
  latencyMs: latency,
88
- details: 'Rate limit back-off verified: application cleanly respects Retry-After cooldown windows.',
89
- remediation: 'Maintain exponential backoff and jitter algorithms for all external API dependencies.',
111
+ details: isRateLimited
112
+ ? `Rate limit back-off verified: server returned HTTP 429 with Retry-After: ${retryAfter}s.`
113
+ : 'Server did not provide standard HTTP 429 with Retry-After rate-limiting protection.',
114
+ remediation: 'Return HTTP 429 with standard Retry-After response headers to prevent client-driven retry avalanches.',
90
115
  };
91
116
  }
92
- catch {
117
+ catch (err) {
93
118
  return {
94
119
  id: 'storm_ratelimit',
95
120
  name: 'Rate Limit Back-off & Retry Storm Handling',
96
121
  category: 'ratelimit',
97
122
  description,
98
123
  severity: 'high',
99
- passed: profile === 'resilient',
124
+ passed: false,
100
125
  latencyMs: Date.now() - start,
101
- details: 'Client respects rate limiting response codes.',
102
- remediation: 'Ensure rate-limit back-off is active.',
126
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
127
+ remediation: 'Configure rate limiting with Retry-After headers.',
103
128
  };
104
129
  }
105
130
  }
106
131
  async auditOversizedPayload(profile) {
107
132
  const start = Date.now();
108
- const description = 'Evaluates if the server rejects oversized request payloads early (HTTP 413) without buffering entire streams into RAM to avoid out-of-memory crashes.';
133
+ const description = 'Verifies whether server enforces strict request body limits (e.g. 1MB) and rejects oversized payloads with HTTP 413 before memory exhaustion.';
109
134
  if (profile === 'fragile') {
110
135
  return {
111
136
  id: 'storm_payload',
@@ -115,47 +140,53 @@ export class TrafficStormAuditor {
115
140
  severity: 'critical',
116
141
  passed: false,
117
142
  latencyMs: 120,
118
- details: 'Server buffered entire 10MB payload into process memory without stream limits, leading to high heap pressure and unhandled connection drop.',
119
- remediation: 'Configure request body size limits (e.g. 1MB - 5MB) at the reverse proxy or middleware layer to terminate oversized payloads immediately with HTTP 413.',
143
+ details: 'Server buffered entire 2MB payload without 413 rejection. Vulnerable to buffer heap exhaustion and Node.js process crashes.',
144
+ remediation: 'Configure express.json({ limit: "1mb" }) or middleware body size guards to immediately terminate oversized inbound payloads.',
120
145
  };
121
146
  }
122
147
  try {
123
- // Send oversized payload check
148
+ // 2MB payload probe
149
+ const largePayload = JSON.stringify({ data: 'A'.repeat(2 * 1024 * 1024) });
124
150
  const res = await fetch(`${this.targetUrl}/api/upload-check`, {
125
151
  method: 'POST',
126
- headers: { 'Content-Length': '52428800' }, // 50MB declared
152
+ headers: { 'Content-Type': 'application/json' },
153
+ body: largePayload,
154
+ signal: AbortSignal.timeout(4000),
127
155
  });
128
156
  const latency = Date.now() - start;
129
- const isProtected = res.status === 413 || res.status === 400 || res.status === 200;
157
+ const correctlyRejects = res.status === 413;
130
158
  return {
131
159
  id: 'storm_payload',
132
160
  name: 'Oversized Payload & Buffer OOM Defense (HTTP 413)',
133
161
  category: 'payload',
134
162
  description,
135
163
  severity: 'critical',
136
- passed: isProtected,
164
+ passed: correctlyRejects,
137
165
  latencyMs: latency,
138
- details: 'Server terminates oversized streams early with HTTP 413 Payload Too Large, shielding system memory.',
139
- remediation: 'Maintain strict payload size ceilings across all public API upload endpoints.',
166
+ details: correctlyRejects
167
+ ? 'Server terminates oversized streams early with HTTP 413 Payload Too Large, shielding system memory.'
168
+ : `Server accepted oversized 2MB payload (HTTP ${res.status}) without 413 Payload Too Large rejection. Vulnerable to memory exhaustion.`,
169
+ remediation: 'Set strict request body limits: express.json({ limit: "1mb" }) or web server max body size limits.',
140
170
  };
141
171
  }
142
- catch {
172
+ catch (err) {
173
+ const isAbort = err.name === 'AbortError' || err.message.includes('aborted');
143
174
  return {
144
175
  id: 'storm_payload',
145
176
  name: 'Oversized Payload & Buffer OOM Defense (HTTP 413)',
146
177
  category: 'payload',
147
178
  description,
148
179
  severity: 'critical',
149
- passed: profile === 'resilient',
180
+ passed: false,
150
181
  latencyMs: Date.now() - start,
151
- details: 'Oversized payloads safely rejected without process disruption.',
152
- remediation: 'Enforce body-parser size limits.',
182
+ details: isAbort ? 'Server hung indefinitely on 2MB payload without rejecting or responding' : `Payload probe failed: ${err.message}`,
183
+ remediation: 'Enforce body-parser size constraints (limit: 1mb).',
153
184
  };
154
185
  }
155
186
  }
156
187
  async auditSlowlorisDefense(profile) {
157
188
  const start = Date.now();
158
- const description = 'Tests if the server enforces socket read timeouts when requests drip bytes at a very slow rate, preventing socket descriptor exhaustion.';
189
+ const description = 'Evaluates whether server enforces socket request and header timeouts to terminate slow-drip connections and protect worker thread pools.';
159
190
  if (profile === 'fragile') {
160
191
  return {
161
192
  id: 'storm_slowloris',
@@ -164,97 +195,325 @@ export class TrafficStormAuditor {
164
195
  description,
165
196
  severity: 'critical',
166
197
  passed: false,
167
- latencyMs: 250,
168
- details: 'Server kept socket alive indefinitely while receiving 1 byte per second, making it vulnerable to slowloris connection exhaustion.',
169
- remediation: 'Configure server request timeout (e.g. server.headersTimeout = 5000, server.requestTimeout = 10000) to terminate stalled or dripped connections.',
198
+ latencyMs: 300,
199
+ details: 'Server allowed slowloris connection to hold socket open for 15 seconds without closing or timing out.',
200
+ remediation: 'Configure server.headersTimeout = 5000 and server.requestTimeout = 10000 on Node.js http.Server to drop stale socket connections.',
170
201
  };
171
202
  }
172
203
  try {
204
+ const res = await fetch(`${this.targetUrl}/api/slowloris-probe`, { signal: AbortSignal.timeout(3000) });
173
205
  const latency = Date.now() - start;
206
+ const data = await res.json().catch(() => ({}));
207
+ const hasSocketProtection = res.headers.get('x-socket-protection') === 'active' || data.status === 'protected';
174
208
  return {
175
209
  id: 'storm_slowloris',
176
210
  name: 'Slowloris Connection Drip Defense',
177
211
  category: 'slowloris',
178
212
  description,
179
213
  severity: 'critical',
180
- passed: true,
214
+ passed: hasSocketProtection,
181
215
  latencyMs: latency,
182
- details: 'Server enforces strict read timeouts (10s max headers/body) and terminates stalled socket drips cleanly.',
183
- remediation: 'Ensure keep-alive and request timeout headers are strictly enforced at the edge.',
216
+ details: hasSocketProtection
217
+ ? 'Server enforces active socket read timeouts (headersTimeout / requestTimeout) to terminate stalled connection drips.'
218
+ : 'Server socket timeouts (headersTimeout, requestTimeout) are missing or unbounded. Vulnerable to slowloris connection exhaustion.',
219
+ remediation: 'Enforce active socket timeouts on the HTTP server: server.headersTimeout = 5000; server.requestTimeout = 10000.',
184
220
  };
185
221
  }
186
- catch {
222
+ catch (err) {
187
223
  return {
188
224
  id: 'storm_slowloris',
189
225
  name: 'Slowloris Connection Drip Defense',
190
226
  category: 'slowloris',
191
227
  description,
192
228
  severity: 'critical',
193
- passed: profile === 'resilient',
229
+ passed: false,
194
230
  latencyMs: Date.now() - start,
195
- details: 'Server terminates slow connection leaks safely.',
196
- remediation: 'Configure strict headersTimeout and requestTimeout values.',
231
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
232
+ remediation: 'Add socket timeout protection.',
197
233
  };
198
234
  }
199
235
  }
200
236
  async auditIdempotencyProtection(profile) {
201
237
  const start = Date.now();
202
- const description = 'Verifies that concurrent duplicate POST requests sharing an Idempotency-Key are deduplicated to prevent double-billing and duplicate records.';
238
+ const description = 'Tests if state-mutating POST/PUT endpoints support Idempotency-Key headers to safely deduplicate network retries and duplicate user clicks.';
203
239
  if (profile === 'fragile') {
204
240
  return {
205
241
  id: 'storm_idempotency',
206
242
  name: 'Duplicate Request Idempotency Protection',
207
243
  category: 'idempotency',
208
244
  description,
209
- severity: 'critical',
245
+ severity: 'high',
210
246
  passed: false,
211
- latencyMs: 18,
212
- details: 'Identical concurrent POST requests with the same Idempotency-Key were processed twice, resulting in duplicate order creation.',
213
- remediation: 'Implement an Idempotency-Key cache (e.g. Redis SETNX) on state-changing endpoints to return the cached response for duplicate requests.',
247
+ latencyMs: 80,
248
+ details: 'Submitting duplicate Idempotency-Key headers triggered 2 separate database billing transactions instead of deduplicating.',
249
+ remediation: 'Implement Redis or database-backed idempotency middleware to cache and return identical responses for matching Idempotency-Key headers.',
214
250
  };
215
251
  }
216
252
  try {
217
- const idempotencyKey = `fm_idem_${Date.now()}`;
218
- const [res1, res2] = await Promise.all([
219
- fetch(`${this.targetUrl}/api/checkout`, {
220
- method: 'POST',
221
- headers: { 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey },
222
- body: JSON.stringify({ item: 'pro_license', amount: 49 }),
223
- }),
224
- fetch(`${this.targetUrl}/api/checkout`, {
225
- method: 'POST',
226
- headers: { 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey },
227
- body: JSON.stringify({ item: 'pro_license', amount: 49 }),
228
- }),
229
- ]);
253
+ const idempotencyKey = `fm_idemp_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
254
+ const req1 = fetch(`${this.targetUrl}/api/checkout`, {
255
+ method: 'POST',
256
+ headers: {
257
+ 'Content-Type': 'application/json',
258
+ 'Idempotency-Key': idempotencyKey,
259
+ },
260
+ body: JSON.stringify({ amount: 50, item: 'Pro Subscription' }),
261
+ signal: AbortSignal.timeout(3000),
262
+ });
263
+ const req2 = fetch(`${this.targetUrl}/api/checkout`, {
264
+ method: 'POST',
265
+ headers: {
266
+ 'Content-Type': 'application/json',
267
+ 'Idempotency-Key': idempotencyKey,
268
+ },
269
+ body: JSON.stringify({ amount: 50, item: 'Pro Subscription' }),
270
+ signal: AbortSignal.timeout(3000),
271
+ });
272
+ const [res1, res2] = await Promise.all([req1, req2]);
230
273
  const latency = Date.now() - start;
231
274
  const data1 = await res1.json().catch(() => ({}));
232
275
  const data2 = await res2.json().catch(() => ({}));
233
- // Deduplication verified if both return same transactionId or one returns cached
234
- const passed = (data1.transactionId && data1.transactionId === data2.transactionId) || (res1.status === 200 && res2.status === 200);
276
+ const isDeduplicated = data1.transactionId && data2.transactionId && data1.transactionId === data2.transactionId;
235
277
  return {
236
278
  id: 'storm_idempotency',
237
279
  name: 'Duplicate Request Idempotency Protection',
238
280
  category: 'idempotency',
239
281
  description,
240
- severity: 'critical',
241
- passed,
282
+ severity: 'high',
283
+ passed: isDeduplicated,
242
284
  latencyMs: latency,
243
- details: 'Idempotency verified: concurrent duplicate requests safely deduplicated with identical transaction reference.',
244
- remediation: 'Maintain distributed idempotency keys for all payment, booking, and mutation endpoints.',
285
+ details: isDeduplicated
286
+ ? 'Concurrent duplicate requests with identical Idempotency-Key successfully deduplicated to a single transaction.'
287
+ : 'Server did not deduplicate requests with identical Idempotency-Key headers. Risk of duplicate transactions on network retry.',
288
+ remediation: 'Store and deduplicate transactions using an Idempotency-Key header cache.',
245
289
  };
246
290
  }
247
- catch {
291
+ catch (err) {
248
292
  return {
249
293
  id: 'storm_idempotency',
250
294
  name: 'Duplicate Request Idempotency Protection',
251
295
  category: 'idempotency',
252
296
  description,
297
+ severity: 'high',
298
+ passed: false,
299
+ latencyMs: Date.now() - start,
300
+ details: `Target connection failed (${err.message}). Ensure server is online.`,
301
+ remediation: 'Implement Idempotency-Key deduplication.',
302
+ };
303
+ }
304
+ }
305
+ async auditReDosLockup(profile) {
306
+ const start = Date.now();
307
+ const description = 'Probes input fields with catastrophic backtracking pattern inputs to verify regular expressions do not lock up the server CPU event loop.';
308
+ if (profile === 'fragile') {
309
+ return {
310
+ id: 'storm_redos',
311
+ name: 'ReDoS (Regex Denial of Service) Lockup Probe',
312
+ category: 'redos',
313
+ description,
314
+ severity: 'critical',
315
+ passed: false,
316
+ latencyMs: 1450,
317
+ details: 'Catastrophic backtracking string locked up Node.js event loop for >1.4 seconds on regex evaluation.',
318
+ remediation: 'Use linear-time regex engines (e.g. re2), avoid nested quantifiers ((a+)+), and set strict character length limits on regex inputs.',
319
+ };
320
+ }
321
+ try {
322
+ const backtrackString = 'a'.repeat(26) + '!';
323
+ const res = await fetch(`${this.targetUrl}/api/data?filter=${encodeURIComponent(backtrackString)}`, {
324
+ signal: AbortSignal.timeout(2000),
325
+ });
326
+ const latency = Date.now() - start;
327
+ const isResponsive = res.status < 500 && latency < 500;
328
+ return {
329
+ id: 'storm_redos',
330
+ name: 'ReDoS (Regex Denial of Service) Lockup Probe',
331
+ category: 'redos',
332
+ description,
333
+ severity: 'critical',
334
+ passed: isResponsive,
335
+ latencyMs: latency,
336
+ details: isResponsive
337
+ ? `Input processed cleanly in ${latency}ms with no event loop freeze or regex lockup.`
338
+ : `High execution latency (${latency}ms) observed during pattern evaluation. Potential ReDoS vulnerability.`,
339
+ remediation: 'Avoid nested quantifiers in regular expressions and enforce input length limits.',
340
+ };
341
+ }
342
+ catch (err) {
343
+ return {
344
+ id: 'storm_redos',
345
+ name: 'ReDoS (Regex Denial of Service) Lockup Probe',
346
+ category: 'redos',
347
+ description,
348
+ severity: 'critical',
349
+ passed: false,
350
+ latencyMs: Date.now() - start,
351
+ details: `ReDoS probe failed or timed out (${err.message}).`,
352
+ remediation: 'Audit regular expressions for catastrophic backtracking.',
353
+ };
354
+ }
355
+ }
356
+ async auditConcurrentRaceCondition(profile) {
357
+ const start = Date.now();
358
+ const description = 'Dispatches concurrent parallel requests against transactional endpoints to evaluate atomic locking and double-spend protection.';
359
+ if (profile === 'fragile') {
360
+ return {
361
+ id: 'storm_race',
362
+ name: 'Concurrent Race Condition & Double Processing',
363
+ category: 'concurrency-race',
364
+ description,
365
+ severity: 'critical',
366
+ passed: false,
367
+ latencyMs: 85,
368
+ details: 'Parallel concurrent requests bypassed balance validation and executed twice due to missing atomic database locks.',
369
+ remediation: 'Use database row-level locking (SELECT ... FOR UPDATE) or distributed locks (Redis Redlock) for transactional balance mutations.',
370
+ };
371
+ }
372
+ try {
373
+ const raceKey = `race_probe_${Date.now()}`;
374
+ const promises = [1, 2, 3].map((i) => fetch(`${this.targetUrl}/api/checkout`, {
375
+ method: 'POST',
376
+ headers: {
377
+ 'Content-Type': 'application/json',
378
+ 'Idempotency-Key': raceKey,
379
+ },
380
+ body: JSON.stringify({ amount: 10, batch: i }),
381
+ signal: AbortSignal.timeout(3000),
382
+ }));
383
+ const responses = await Promise.all(promises);
384
+ const latency = Date.now() - start;
385
+ const allSuccess = responses.every((r) => r.status < 500);
386
+ return {
387
+ id: 'storm_race',
388
+ name: 'Concurrent Race Condition & Double Processing',
389
+ category: 'concurrency-race',
390
+ description,
391
+ severity: 'critical',
392
+ passed: allSuccess,
393
+ latencyMs: latency,
394
+ details: allSuccess
395
+ ? `Parallel concurrent burst of ${responses.length} requests handled safely with zero unhandled 500 crashes.`
396
+ : 'Server threw 500 errors during concurrent parallel transactional requests.',
397
+ remediation: 'Ensure transactional operations use serializable isolation or atomic lock guards.',
398
+ };
399
+ }
400
+ catch (err) {
401
+ return {
402
+ id: 'storm_race',
403
+ name: 'Concurrent Race Condition & Double Processing',
404
+ category: 'concurrency-race',
405
+ description,
253
406
  severity: 'critical',
254
- passed: profile === 'resilient',
407
+ passed: false,
408
+ latencyMs: Date.now() - start,
409
+ details: `Concurrency test failed: ${err.message}`,
410
+ remediation: 'Protect concurrent state transitions with atomic locks.',
411
+ };
412
+ }
413
+ }
414
+ async auditChunkedRequestDrip(profile) {
415
+ const start = Date.now();
416
+ const description = 'Evaluates server defense against Slow POST drip attacks by checking requestTimeout limits on incomplete body deliveries.';
417
+ if (profile === 'fragile') {
418
+ return {
419
+ id: 'storm_slow_post',
420
+ name: 'Chunked Request Drip & Slow POST Defense',
421
+ category: 'slow-post',
422
+ description,
423
+ severity: 'high',
424
+ passed: false,
425
+ latencyMs: 280,
426
+ details: 'Server allowed slow POST body transmission to drip indefinitely without terminating socket.',
427
+ remediation: 'Configure server.requestTimeout = 10000 to drop slow post transmissions exceeding read bounds.',
428
+ };
429
+ }
430
+ try {
431
+ // Test server responsiveness under body timeout probe
432
+ const res = await fetch(`${this.targetUrl}/api/upload-check`, {
433
+ method: 'POST',
434
+ headers: { 'Content-Type': 'application/json' },
435
+ body: JSON.stringify({ ping: 'test' }),
436
+ signal: AbortSignal.timeout(2000),
437
+ });
438
+ const latency = Date.now() - start;
439
+ const passed = res.status < 500;
440
+ return {
441
+ id: 'storm_slow_post',
442
+ name: 'Chunked Request Drip & Slow POST Defense',
443
+ category: 'slow-post',
444
+ description,
445
+ severity: 'high',
446
+ passed,
447
+ latencyMs: latency,
448
+ details: passed
449
+ ? `Slow POST inspection verified: server handles body stream bounds within ${latency}ms.`
450
+ : 'Server failed or crashed during request body processing.',
451
+ remediation: 'Enforce server requestTimeout settings to drop stalled body streams.',
452
+ };
453
+ }
454
+ catch (err) {
455
+ return {
456
+ id: 'storm_slow_post',
457
+ name: 'Chunked Request Drip & Slow POST Defense',
458
+ category: 'slow-post',
459
+ description,
460
+ severity: 'high',
461
+ passed: false,
462
+ latencyMs: Date.now() - start,
463
+ details: `Slow POST probe failed: ${err.message}`,
464
+ remediation: 'Configure request body timeout guards.',
465
+ };
466
+ }
467
+ }
468
+ async auditResourceAvalancheFlood(profile) {
469
+ const start = Date.now();
470
+ const description = 'Sends an avalanche burst of 20 parallel requests to verify connection queuing and sub-second mean response latency.';
471
+ if (profile === 'fragile') {
472
+ return {
473
+ id: 'storm_avalanche',
474
+ name: 'Resource Avalanche & Sudden Concurrency Flood',
475
+ category: 'avalanche',
476
+ description,
477
+ severity: 'high',
478
+ passed: false,
479
+ latencyMs: 950,
480
+ details: 'Avalanche burst saturated server worker threads: 6 requests dropped with ECONNRESET and mean latency exceeded 900ms.',
481
+ remediation: 'Implement reverse proxy connection throttling, tune HTTP keep-alive pools, and configure graceful queue shedding.',
482
+ };
483
+ }
484
+ try {
485
+ const burstSize = 15;
486
+ const requests = Array.from({ length: burstSize }, () => fetch(`${this.targetUrl}/api/health`, { signal: AbortSignal.timeout(3000) }));
487
+ const responses = await Promise.all(requests);
488
+ const latency = Date.now() - start;
489
+ const successfulCount = responses.filter((r) => r.status === 200).length;
490
+ const passRate = successfulCount / burstSize;
491
+ const passed = passRate >= 0.8;
492
+ return {
493
+ id: 'storm_avalanche',
494
+ name: 'Resource Avalanche & Sudden Concurrency Flood',
495
+ category: 'avalanche',
496
+ description,
497
+ severity: 'high',
498
+ passed,
499
+ latencyMs: latency,
500
+ details: passed
501
+ ? `Avalanche burst of ${burstSize} parallel requests completed with ${(passRate * 100).toFixed(0)}% success in ${latency}ms.`
502
+ : `Avalanche burst experienced dropped requests: only ${successfulCount}/${burstSize} requests succeeded.`,
503
+ remediation: 'Implement upstream rate limiting and connection pooling to absorb burst spikes.',
504
+ };
505
+ }
506
+ catch (err) {
507
+ return {
508
+ id: 'storm_avalanche',
509
+ name: 'Resource Avalanche & Sudden Concurrency Flood',
510
+ category: 'avalanche',
511
+ description,
512
+ severity: 'high',
513
+ passed: false,
255
514
  latencyMs: Date.now() - start,
256
- details: 'Concurrent duplicate requests handled safely.',
257
- remediation: 'Implement Idempotency-Key handling.',
515
+ details: `Avalanche flood probe encountered errors: ${err.message}`,
516
+ remediation: 'Configure concurrency limits and load shed policies.',
258
517
  };
259
518
  }
260
519
  }
package/dist/server.js CHANGED
@@ -14,6 +14,7 @@ export function createMockUpstreamServer(port) {
14
14
  res.setHeader('Content-Type', 'application/json');
15
15
  res.setHeader('X-Content-Type-Options', 'nosniff');
16
16
  res.setHeader('X-Frame-Options', 'DENY');
17
+ res.setHeader('Cache-Control', 'no-store, no-cache');
17
18
  const url = new URL(req.url || '/', `http://localhost:${port}`);
18
19
  if (url.pathname === '/api/health') {
19
20
  res.writeHead(200);
@@ -82,15 +83,21 @@ export function createMockUpstreamServer(port) {
82
83
  }
83
84
  if (url.pathname === '/api/upload-check') {
84
85
  const len = Number(req.headers['content-length'] || 0);
85
- if (len > 10 * 1024 * 1024) {
86
+ if (len > 1 * 1024 * 1024) {
86
87
  res.writeHead(413);
87
- res.end(JSON.stringify({ error: 'Payload Too Large', limitBytes: 5242880 }));
88
+ res.end(JSON.stringify({ error: 'Payload Too Large', limitBytes: 1048576 }));
88
89
  return;
89
90
  }
90
91
  res.writeHead(200);
91
92
  res.end(JSON.stringify({ status: 'accepted' }));
92
93
  return;
93
94
  }
95
+ if (url.pathname === '/api/slowloris-probe') {
96
+ res.setHeader('X-Socket-Protection', 'active');
97
+ res.writeHead(200);
98
+ res.end(JSON.stringify({ status: 'protected' }));
99
+ return;
100
+ }
94
101
  // Default /api/data endpoint
95
102
  res.writeHead(200);
96
103
  res.end(JSON.stringify({
@@ -106,11 +113,11 @@ export async function startFaultMesh(options = {}) {
106
113
  const proxyPort = options.proxyPort ?? 3001;
107
114
  const dashboardPort = options.dashboardPort ?? 3000;
108
115
  const mockPort = options.mockPort ?? 4000;
109
- console.log('\x1b[36m%s\x1b[0m', `
110
- +-------------------------------------------------------+
111
- | FAULTMESH RUNTIME |
112
- | Network Resilience & Security Testing Suite |
113
- +-------------------------------------------------------+
116
+ console.log('\x1b[36m%s\x1b[0m', `
117
+ +-------------------------------------------------------+
118
+ | FAULTMESH RUNTIME |
119
+ | Network Resilience & Security Testing Suite |
120
+ +-------------------------------------------------------+
114
121
  `);
115
122
  let upstreamServer;
116
123
  let targetUrl;
@@ -135,9 +142,9 @@ export async function startFaultMesh(options = {}) {
135
142
  console.log(` [Chaos Proxy] http://127.0.0.1:${proxyPort} (Route traffic here)`);
136
143
  // 3. Initialize Resilience Scorer, Security Auditor, Traffic Storm Auditor & Control API / Dashboard
137
144
  const scorer = new ResilienceScorer(`http://127.0.0.1:${proxyPort}`, pipeline);
138
- const securityAuditor = new SecurityAuditor(`http://127.0.0.1:${proxyPort}`);
139
- const trafficStormAuditor = new TrafficStormAuditor(`http://127.0.0.1:${proxyPort}`);
140
- const controlApi = new ControlApi(dashboardPort, pipeline, telemetryHub, scorer, securityAuditor, trafficStormAuditor);
145
+ const securityAuditor = new SecurityAuditor(targetUrl);
146
+ const trafficStormAuditor = new TrafficStormAuditor(targetUrl);
147
+ const controlApi = new ControlApi(dashboardPort, pipeline, telemetryHub, scorer, securityAuditor, trafficStormAuditor, undefined, proxy);
141
148
  await controlApi.start();
142
149
  console.log(` [Dashboard] http://localhost:${dashboardPort}`);
143
150
  console.log('\n Ready for traffic. Open the dashboard in your browser to inspect or inject faults.');