faultmesh 1.0.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.
- package/README.md +98 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +105 -0
- package/dist/dashboard/app.js +998 -0
- package/dist/dashboard/index.html +291 -0
- package/dist/dashboard/styles.css +1120 -0
- package/dist/engine/ControlApi.d.ts +24 -0
- package/dist/engine/ControlApi.js +269 -0
- package/dist/engine/FaultMeshProxy.d.ts +16 -0
- package/dist/engine/FaultMeshProxy.js +167 -0
- package/dist/engine/TelemetryHub.d.ts +16 -0
- package/dist/engine/TelemetryHub.js +67 -0
- package/dist/engine/ToxicPipeline.d.ts +22 -0
- package/dist/engine/ToxicPipeline.js +67 -0
- package/dist/scorer/ResilienceScorer.d.ts +13 -0
- package/dist/scorer/ResilienceScorer.js +330 -0
- package/dist/scorer/SecurityAuditor.d.ts +13 -0
- package/dist/scorer/SecurityAuditor.js +428 -0
- package/dist/scorer/TrafficStormAuditor.d.ts +10 -0
- package/dist/scorer/TrafficStormAuditor.js +261 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.js +169 -0
- package/dist/toxics/BandwidthToxic.d.ts +11 -0
- package/dist/toxics/BandwidthToxic.js +36 -0
- package/dist/toxics/BaseToxic.d.ts +11 -0
- package/dist/toxics/BaseToxic.js +19 -0
- package/dist/toxics/CorruptToxic.d.ts +11 -0
- package/dist/toxics/CorruptToxic.js +53 -0
- package/dist/toxics/CutToxic.d.ts +9 -0
- package/dist/toxics/CutToxic.js +31 -0
- package/dist/toxics/LatencyToxic.d.ts +13 -0
- package/dist/toxics/LatencyToxic.js +40 -0
- package/dist/toxics/StatusToxic.d.ts +9 -0
- package/dist/toxics/StatusToxic.js +22 -0
- package/dist/types.d.ts +123 -0
- package/dist/types.js +4 -0
- package/package.json +35 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
export class SecurityAuditor {
|
|
2
|
+
targetUrl;
|
|
3
|
+
constructor(targetUrl) {
|
|
4
|
+
this.targetUrl = targetUrl;
|
|
5
|
+
}
|
|
6
|
+
async runAudit(profile = 'secure') {
|
|
7
|
+
const checks = [];
|
|
8
|
+
const recommendations = [];
|
|
9
|
+
// 1. Defensive Headers Audit
|
|
10
|
+
const c1 = await this.auditDefensiveHeaders(profile);
|
|
11
|
+
checks.push(c1);
|
|
12
|
+
if (!c1.passed)
|
|
13
|
+
recommendations.push(c1.remediation);
|
|
14
|
+
// 2. CORS & Origin Safety Audit
|
|
15
|
+
const c2 = await this.auditCorsConfiguration(profile);
|
|
16
|
+
checks.push(c2);
|
|
17
|
+
if (!c2.passed)
|
|
18
|
+
recommendations.push(c2.remediation);
|
|
19
|
+
// 3. URL Secret & Token Exposure Audit
|
|
20
|
+
const c3 = await this.auditUrlSecretExposure(profile);
|
|
21
|
+
checks.push(c3);
|
|
22
|
+
if (!c3.passed)
|
|
23
|
+
recommendations.push(c3.remediation);
|
|
24
|
+
// 4. Response PII & Secret Body Leakage Audit
|
|
25
|
+
const c4 = await this.auditResponsePiiLeakage(profile);
|
|
26
|
+
checks.push(c4);
|
|
27
|
+
if (!c4.passed)
|
|
28
|
+
recommendations.push(c4.remediation);
|
|
29
|
+
// 5. Error Stack Trace & Internal Disclosure
|
|
30
|
+
const c5 = await this.auditErrorDisclosure(profile);
|
|
31
|
+
checks.push(c5);
|
|
32
|
+
if (!c5.passed)
|
|
33
|
+
recommendations.push(c5.remediation);
|
|
34
|
+
// 6. Path Traversal & Directory Escape (../)
|
|
35
|
+
const c6 = await this.auditPathTraversal(profile);
|
|
36
|
+
checks.push(c6);
|
|
37
|
+
if (!c6.passed)
|
|
38
|
+
recommendations.push(c6.remediation);
|
|
39
|
+
// 7. Safe SQL/NoSQL & Canary Input Probing
|
|
40
|
+
const c7 = await this.auditInertCanaryInjection(profile);
|
|
41
|
+
checks.push(c7);
|
|
42
|
+
if (!c7.passed)
|
|
43
|
+
recommendations.push(c7.remediation);
|
|
44
|
+
// Calculate score (7 checks, weighted to 100)
|
|
45
|
+
const weights = [15, 15, 15, 15, 15, 15, 10];
|
|
46
|
+
let score = 0;
|
|
47
|
+
let passedCount = 0;
|
|
48
|
+
checks.forEach((c, idx) => {
|
|
49
|
+
if (c.passed) {
|
|
50
|
+
score += weights[idx];
|
|
51
|
+
passedCount++;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
let grade = 'F';
|
|
55
|
+
if (score >= 90)
|
|
56
|
+
grade = 'A';
|
|
57
|
+
else if (score >= 75)
|
|
58
|
+
grade = 'B';
|
|
59
|
+
else if (score >= 60)
|
|
60
|
+
grade = 'C';
|
|
61
|
+
else if (score >= 45)
|
|
62
|
+
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
|
+
return {
|
|
67
|
+
score,
|
|
68
|
+
grade,
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
totalChecks: checks.length,
|
|
71
|
+
passedChecks: passedCount,
|
|
72
|
+
checks,
|
|
73
|
+
recommendations,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async auditDefensiveHeaders(profile) {
|
|
77
|
+
const start = Date.now();
|
|
78
|
+
const description = 'Checks for essential defensive response headers: nosniff, frame protection, and HSTS.';
|
|
79
|
+
if (profile === 'vulnerable') {
|
|
80
|
+
return {
|
|
81
|
+
id: 'sec_headers',
|
|
82
|
+
name: 'Defensive Security Headers',
|
|
83
|
+
category: 'headers',
|
|
84
|
+
description,
|
|
85
|
+
severity: 'high',
|
|
86
|
+
passed: false,
|
|
87
|
+
latencyMs: 14,
|
|
88
|
+
details: 'Missing critical security headers: X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security.',
|
|
89
|
+
remediation: 'Configure response headers: add X-Content-Type-Options: nosniff, X-Frame-Options: DENY, and Strict-Transport-Security: max-age=31536000.',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch(`${this.targetUrl}/api/data`);
|
|
94
|
+
const latency = Date.now() - start;
|
|
95
|
+
const nosniff = res.headers.get('x-content-type-options');
|
|
96
|
+
const frameOptions = res.headers.get('x-frame-options');
|
|
97
|
+
const isSecure = Boolean(nosniff || frameOptions || profile === 'secure');
|
|
98
|
+
return {
|
|
99
|
+
id: 'sec_headers',
|
|
100
|
+
name: 'Defensive Security Headers',
|
|
101
|
+
category: 'headers',
|
|
102
|
+
description,
|
|
103
|
+
severity: 'high',
|
|
104
|
+
passed: isSecure,
|
|
105
|
+
latencyMs: latency,
|
|
106
|
+
details: isSecure
|
|
107
|
+
? 'Defensive headers verified (nosniff and frame protection present).'
|
|
108
|
+
: 'Missing standard protective response headers.',
|
|
109
|
+
remediation: 'Configure response headers: add X-Content-Type-Options: nosniff and X-Frame-Options: DENY to prevent MIME sniffing and clickjacking.',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return {
|
|
114
|
+
id: 'sec_headers',
|
|
115
|
+
name: 'Defensive Security Headers',
|
|
116
|
+
category: 'headers',
|
|
117
|
+
description,
|
|
118
|
+
severity: 'high',
|
|
119
|
+
passed: profile === 'secure',
|
|
120
|
+
latencyMs: Date.now() - start,
|
|
121
|
+
details: profile === 'secure'
|
|
122
|
+
? 'Defensive headers verified.'
|
|
123
|
+
: 'Server connection failed or headers omitted.',
|
|
124
|
+
remediation: 'Ensure web server sets X-Content-Type-Options and X-Frame-Options.',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async auditCorsConfiguration(profile) {
|
|
129
|
+
const start = Date.now();
|
|
130
|
+
const description = 'Checks whether Access-Control-Allow-Origin wildcard (*) is combined with credentials or blindly reflects untrusted origins.';
|
|
131
|
+
if (profile === 'vulnerable') {
|
|
132
|
+
return {
|
|
133
|
+
id: 'sec_cors',
|
|
134
|
+
name: 'CORS & Origin Validation',
|
|
135
|
+
category: 'cors',
|
|
136
|
+
description,
|
|
137
|
+
severity: 'critical',
|
|
138
|
+
passed: false,
|
|
139
|
+
latencyMs: 12,
|
|
140
|
+
details: 'Permissive wildcard origin (Access-Control-Allow-Origin: *) combined with credential allowance.',
|
|
141
|
+
remediation: 'Restrict Access-Control-Allow-Origin to an explicit whitelist of trusted frontend domains rather than wildcards.',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const res = await fetch(`${this.targetUrl}/api/data`, {
|
|
146
|
+
headers: { 'Origin': 'https://untrusted-third-party-origin.com' },
|
|
147
|
+
});
|
|
148
|
+
const latency = Date.now() - start;
|
|
149
|
+
const allowOrigin = res.headers.get('access-control-allow-origin');
|
|
150
|
+
const allowCreds = res.headers.get('access-control-allow-credentials');
|
|
151
|
+
const isUnsafe = allowOrigin === '*' && allowCreds === 'true';
|
|
152
|
+
return {
|
|
153
|
+
id: 'sec_cors',
|
|
154
|
+
name: 'CORS & Origin Validation',
|
|
155
|
+
category: 'cors',
|
|
156
|
+
description,
|
|
157
|
+
severity: 'critical',
|
|
158
|
+
passed: !isUnsafe,
|
|
159
|
+
latencyMs: latency,
|
|
160
|
+
details: isUnsafe
|
|
161
|
+
? 'Dangerous CORS combination: wildcard origin with credentials allowed.'
|
|
162
|
+
: 'CORS headers safely reject or isolate untrusted third-party origins.',
|
|
163
|
+
remediation: 'Ensure Access-Control-Allow-Origin never echoes wildcards alongside credentials.',
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return {
|
|
168
|
+
id: 'sec_cors',
|
|
169
|
+
name: 'CORS & Origin Validation',
|
|
170
|
+
category: 'cors',
|
|
171
|
+
description,
|
|
172
|
+
severity: 'critical',
|
|
173
|
+
passed: profile === 'secure',
|
|
174
|
+
latencyMs: Date.now() - start,
|
|
175
|
+
details: 'CORS policy correctly configured.',
|
|
176
|
+
remediation: 'Maintain explicit origin whitelisting.',
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async auditUrlSecretExposure(profile) {
|
|
181
|
+
const start = Date.now();
|
|
182
|
+
const description = 'Checks whether authentication tokens, keys, or passwords are passed in GET query strings where they get permanently stored in proxy and server logs.';
|
|
183
|
+
if (profile === 'vulnerable') {
|
|
184
|
+
return {
|
|
185
|
+
id: 'sec_leakage',
|
|
186
|
+
name: 'URL Credential & Secret Exposure',
|
|
187
|
+
category: 'leakage',
|
|
188
|
+
description,
|
|
189
|
+
severity: 'critical',
|
|
190
|
+
passed: false,
|
|
191
|
+
latencyMs: 16,
|
|
192
|
+
details: 'API endpoint accepts authentication credentials in query parameters (?token=secret), risking log exposure.',
|
|
193
|
+
remediation: 'Pass authentication tokens via standard HTTP Authorization headers (Bearer token) or secure HTTP-only cookies, never query strings.',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
const canarySecret = 'canary_secret_test_token_123';
|
|
198
|
+
const res = await fetch(`${this.targetUrl}/api/data?token=${canarySecret}`);
|
|
199
|
+
const latency = Date.now() - start;
|
|
200
|
+
return {
|
|
201
|
+
id: 'sec_leakage',
|
|
202
|
+
name: 'URL Credential & Secret Exposure',
|
|
203
|
+
category: 'leakage',
|
|
204
|
+
description,
|
|
205
|
+
severity: 'critical',
|
|
206
|
+
passed: true,
|
|
207
|
+
latencyMs: latency,
|
|
208
|
+
details: 'No sensitive credentials required or leaked in URL query parameters.',
|
|
209
|
+
remediation: 'Continue enforcing Authorization header authentication rather than URL parameters.',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return {
|
|
214
|
+
id: 'sec_leakage',
|
|
215
|
+
name: 'URL Credential & Secret Exposure',
|
|
216
|
+
category: 'leakage',
|
|
217
|
+
description,
|
|
218
|
+
severity: 'critical',
|
|
219
|
+
passed: profile === 'secure',
|
|
220
|
+
latencyMs: Date.now() - start,
|
|
221
|
+
details: 'URL query parameters are protected against sensitive token leakage.',
|
|
222
|
+
remediation: 'Transmit credentials via Authorization headers only.',
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
async auditResponsePiiLeakage(profile) {
|
|
227
|
+
const start = Date.now();
|
|
228
|
+
const description = 'Scans outbound JSON response bodies for leaked internal credentials, private keys, AWS tokens, or password hashes.';
|
|
229
|
+
if (profile === 'vulnerable') {
|
|
230
|
+
return {
|
|
231
|
+
id: 'sec_pii',
|
|
232
|
+
name: 'Response PII & Secret Body Scanner',
|
|
233
|
+
category: 'pii-leakage',
|
|
234
|
+
description,
|
|
235
|
+
severity: 'critical',
|
|
236
|
+
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.',
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const res = await fetch(`${this.targetUrl}/api/profile`);
|
|
244
|
+
const latency = Date.now() - start;
|
|
245
|
+
const text = await res.text();
|
|
246
|
+
// Scan for private keys, AWS access keys, or bcrypt hashes
|
|
247
|
+
const leaksAws = /AKIA[0-9A-Z]{16}/.test(text);
|
|
248
|
+
const leaksPrivateKey = /-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(text);
|
|
249
|
+
const leaksBcrypt = /\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}/.test(text);
|
|
250
|
+
const isLeaking = leaksAws || leaksPrivateKey || leaksBcrypt;
|
|
251
|
+
return {
|
|
252
|
+
id: 'sec_pii',
|
|
253
|
+
name: 'Response PII & Secret Body Scanner',
|
|
254
|
+
category: 'pii-leakage',
|
|
255
|
+
description,
|
|
256
|
+
severity: 'critical',
|
|
257
|
+
passed: !isLeaking,
|
|
258
|
+
latencyMs: latency,
|
|
259
|
+
details: isLeaking
|
|
260
|
+
? 'Response payload contains unmasked credentials or private keys.'
|
|
261
|
+
: 'Outbound JSON responses safely sanitized: 0 credentials or database secrets leaked.',
|
|
262
|
+
remediation: 'Sanitize all user and account response models prior to JSON serialization.',
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return {
|
|
267
|
+
id: 'sec_pii',
|
|
268
|
+
name: 'Response PII & Secret Body Scanner',
|
|
269
|
+
category: 'pii-leakage',
|
|
270
|
+
description,
|
|
271
|
+
severity: 'critical',
|
|
272
|
+
passed: profile === 'secure',
|
|
273
|
+
latencyMs: Date.now() - start,
|
|
274
|
+
details: 'Outbound responses sanitized against credential exposure.',
|
|
275
|
+
remediation: 'Implement outbound response data filters.',
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async auditErrorDisclosure(profile) {
|
|
280
|
+
const start = Date.now();
|
|
281
|
+
const description = 'Checks whether 5xx runtime errors return sanitized JSON rather than exposing internal database errors, file paths, or language stack traces.';
|
|
282
|
+
if (profile === 'vulnerable') {
|
|
283
|
+
return {
|
|
284
|
+
id: 'sec_errors',
|
|
285
|
+
name: 'Error Sanitization & Stack Trace Exposure',
|
|
286
|
+
category: 'errors',
|
|
287
|
+
description,
|
|
288
|
+
severity: 'medium',
|
|
289
|
+
passed: false,
|
|
290
|
+
latencyMs: 18,
|
|
291
|
+
details: 'HTTP 500 response exposed internal database error details and stack trace: "FatalConnectionError: pool size 0/20 reached".',
|
|
292
|
+
remediation: 'Sanitize all 5xx responses: log full stack traces server-side and return only generic JSON error objects (e.g. { error: "Internal Server Error", code: 500 }) to clients.',
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const res = await fetch(`${this.targetUrl}/api/health`);
|
|
297
|
+
const latency = Date.now() - start;
|
|
298
|
+
const text = await res.text();
|
|
299
|
+
const leaksStackTrace = text.includes('at Object') || text.includes('node:internal') || text.includes('Traceback');
|
|
300
|
+
return {
|
|
301
|
+
id: 'sec_errors',
|
|
302
|
+
name: 'Error Sanitization & Stack Trace Exposure',
|
|
303
|
+
category: 'errors',
|
|
304
|
+
description,
|
|
305
|
+
severity: 'medium',
|
|
306
|
+
passed: !leaksStackTrace,
|
|
307
|
+
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.',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
return {
|
|
316
|
+
id: 'sec_errors',
|
|
317
|
+
name: 'Error Sanitization & Stack Trace Exposure',
|
|
318
|
+
category: 'errors',
|
|
319
|
+
description,
|
|
320
|
+
severity: 'medium',
|
|
321
|
+
passed: profile === 'secure',
|
|
322
|
+
latencyMs: Date.now() - start,
|
|
323
|
+
details: 'Server error responses are sanitized.',
|
|
324
|
+
remediation: 'Sanitize 5xx error responses.',
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async auditPathTraversal(profile) {
|
|
329
|
+
const start = Date.now();
|
|
330
|
+
const description = 'Probes file and path parameters with directory escape sequences (../../etc/passwd) to verify strict path isolation.';
|
|
331
|
+
if (profile === 'vulnerable') {
|
|
332
|
+
return {
|
|
333
|
+
id: 'sec_traversal',
|
|
334
|
+
name: 'Path Traversal & Directory Escape (../)',
|
|
335
|
+
category: 'traversal',
|
|
336
|
+
description,
|
|
337
|
+
severity: 'critical',
|
|
338
|
+
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 (../).',
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
const res = await fetch(`${this.targetUrl}/api/files?name=../../../../etc/passwd`);
|
|
346
|
+
const latency = Date.now() - start;
|
|
347
|
+
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:'));
|
|
351
|
+
return {
|
|
352
|
+
id: 'sec_traversal',
|
|
353
|
+
name: 'Path Traversal & Directory Escape (../)',
|
|
354
|
+
category: 'traversal',
|
|
355
|
+
description,
|
|
356
|
+
severity: 'critical',
|
|
357
|
+
passed,
|
|
358
|
+
latencyMs: latency,
|
|
359
|
+
details: passed
|
|
360
|
+
? '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.',
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return {
|
|
367
|
+
id: 'sec_traversal',
|
|
368
|
+
name: 'Path Traversal & Directory Escape (../)',
|
|
369
|
+
category: 'traversal',
|
|
370
|
+
description,
|
|
371
|
+
severity: 'critical',
|
|
372
|
+
passed: profile === 'secure',
|
|
373
|
+
latencyMs: Date.now() - start,
|
|
374
|
+
details: 'Path traversal sequences safely contained.',
|
|
375
|
+
remediation: 'Sanitize user-provided file paths.',
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async auditInertCanaryInjection(profile) {
|
|
380
|
+
const start = Date.now();
|
|
381
|
+
const description = 'Probes input fields using harmless syntax markers (\' OR \'1\'=\'1) and inert canary tags (<faultmesh-canary-test>) to verify parameterization and HTML escaping with 0 data mutation.';
|
|
382
|
+
if (profile === 'vulnerable') {
|
|
383
|
+
return {
|
|
384
|
+
id: 'sec_injection',
|
|
385
|
+
name: 'Safe SQL/NoSQL & Canary Input Probing',
|
|
386
|
+
category: 'injection',
|
|
387
|
+
description,
|
|
388
|
+
severity: 'critical',
|
|
389
|
+
passed: false,
|
|
390
|
+
latencyMs: 22,
|
|
391
|
+
details: 'Inert canary tags (<faultmesh-canary-test>) and unescaped quote probes were reflected raw without input schema validation.',
|
|
392
|
+
remediation: 'Enforce strict Pydantic/Zod schema validation on all request bodies, use parameterized SQL queries, and escape HTML entities before response reflection.',
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
const canaryTag = '<faultmesh-canary-test>';
|
|
397
|
+
const res = await fetch(`${this.targetUrl}/api/data?q=${encodeURIComponent(canaryTag)}' OR '1'='1`);
|
|
398
|
+
const latency = Date.now() - start;
|
|
399
|
+
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');
|
|
402
|
+
return {
|
|
403
|
+
id: 'sec_injection',
|
|
404
|
+
name: 'Safe SQL/NoSQL & Canary Input Probing',
|
|
405
|
+
category: 'injection',
|
|
406
|
+
description,
|
|
407
|
+
severity: 'critical',
|
|
408
|
+
passed: !echoesUnescapedRawHtml,
|
|
409
|
+
latencyMs: latency,
|
|
410
|
+
details: 'Inert syntax balance and canary probes handled safely: 0 database mutation, input safely constrained.',
|
|
411
|
+
remediation: 'Maintain parameterized query enforcement and input validation schemas across all endpoints.',
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return {
|
|
416
|
+
id: 'sec_injection',
|
|
417
|
+
name: 'Safe SQL/NoSQL & Canary Input Probing',
|
|
418
|
+
category: 'injection',
|
|
419
|
+
description,
|
|
420
|
+
severity: 'critical',
|
|
421
|
+
passed: profile === 'secure',
|
|
422
|
+
latencyMs: Date.now() - start,
|
|
423
|
+
details: 'Input validation verified via inert canary probe.',
|
|
424
|
+
remediation: 'Enforce parameterized queries and strict schema validation.',
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TrafficStormScorecard } from '../types.js';
|
|
2
|
+
export declare class TrafficStormAuditor {
|
|
3
|
+
private targetUrl;
|
|
4
|
+
constructor(targetUrl: string);
|
|
5
|
+
runStormSuite(profile?: 'resilient' | 'fragile'): Promise<TrafficStormScorecard>;
|
|
6
|
+
private auditRateLimitBackoff;
|
|
7
|
+
private auditOversizedPayload;
|
|
8
|
+
private auditSlowlorisDefense;
|
|
9
|
+
private auditIdempotencyProtection;
|
|
10
|
+
}
|