mitigator 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/LICENSE.md +15 -0
- package/README.md +355 -0
- package/dist/bin/mitigator-audit.cjs +187 -0
- package/dist/bin/mitigator-audit.cjs.map +1 -0
- package/dist/bin/mitigator-audit.d.cts +14 -0
- package/dist/bin/mitigator-audit.d.ts +14 -0
- package/dist/bin/mitigator-audit.js +135 -0
- package/dist/bin/mitigator-audit.js.map +1 -0
- package/dist/chunk-TT2DUALY.js +123 -0
- package/dist/chunk-TT2DUALY.js.map +1 -0
- package/dist/index.cjs +1643 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1116 -0
- package/dist/index.d.ts +1116 -0
- package/dist/index.js +1501 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1501 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__export,
|
|
3
|
+
scanForSecrets,
|
|
4
|
+
validate_exports
|
|
5
|
+
} from "./chunk-TT2DUALY.js";
|
|
6
|
+
|
|
7
|
+
// src/sanitize/index.ts
|
|
8
|
+
var sanitize_exports = {};
|
|
9
|
+
__export(sanitize_exports, {
|
|
10
|
+
ALLOWLIST: () => ALLOWLIST,
|
|
11
|
+
escapeHtml: () => escapeHtml,
|
|
12
|
+
isSafeUrl: () => isSafeUrl,
|
|
13
|
+
preventDOMClobbering: () => preventDOMClobbering,
|
|
14
|
+
sanitizeHtml: () => sanitizeHtml,
|
|
15
|
+
sanitizeMediaTags: () => sanitizeMediaTags,
|
|
16
|
+
stripTags: () => stripTags
|
|
17
|
+
});
|
|
18
|
+
import sanitizeHtmlLib from "sanitize-html";
|
|
19
|
+
var escapeHtml = (input) => {
|
|
20
|
+
const map = {
|
|
21
|
+
"&": "&",
|
|
22
|
+
"<": "<",
|
|
23
|
+
">": ">",
|
|
24
|
+
'"': """,
|
|
25
|
+
"'": "'"
|
|
26
|
+
};
|
|
27
|
+
return input.replaceAll(/[&<>"']/g, (m) => map[m]);
|
|
28
|
+
};
|
|
29
|
+
var stripTags = (input) => {
|
|
30
|
+
return input.replaceAll(/<[^>]*>?/gm, "");
|
|
31
|
+
};
|
|
32
|
+
var ALLOWLIST = {
|
|
33
|
+
b: [],
|
|
34
|
+
i: [],
|
|
35
|
+
em: [],
|
|
36
|
+
strong: [],
|
|
37
|
+
p: ["style"],
|
|
38
|
+
a: ["href", "title", "target"],
|
|
39
|
+
ul: [],
|
|
40
|
+
ol: [],
|
|
41
|
+
li: [],
|
|
42
|
+
br: [],
|
|
43
|
+
span: ["style"]
|
|
44
|
+
};
|
|
45
|
+
var sanitizeHtml = (html, rules = ALLOWLIST) => {
|
|
46
|
+
return sanitizeHtmlLib(html, {
|
|
47
|
+
allowedTags: Object.keys(rules),
|
|
48
|
+
allowedAttributes: rules,
|
|
49
|
+
allowedSchemes: ["http", "https", "mailto", "tel"],
|
|
50
|
+
allowProtocolRelative: false,
|
|
51
|
+
enforceHtmlBoundary: true
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
var preventDOMClobbering = (html, prefix = "sk-") => {
|
|
55
|
+
const attrRegex = /(\s(id|name)\s*=\s*)(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi;
|
|
56
|
+
return html.replaceAll(attrRegex, (_match, attrPrefix, _attrName, val1, val2, val3) => {
|
|
57
|
+
const value = val1 || val2 || val3;
|
|
58
|
+
let quote;
|
|
59
|
+
if (val3) {
|
|
60
|
+
quote = "";
|
|
61
|
+
} else if (val1) {
|
|
62
|
+
quote = '"';
|
|
63
|
+
} else {
|
|
64
|
+
quote = "'";
|
|
65
|
+
}
|
|
66
|
+
return `${attrPrefix}${quote}${prefix}${value}${quote}`;
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
var sanitizeMediaTags = (html) => {
|
|
70
|
+
const dangerousTags = [
|
|
71
|
+
"script",
|
|
72
|
+
"animate",
|
|
73
|
+
"set",
|
|
74
|
+
"animateMotion",
|
|
75
|
+
"animateTransform",
|
|
76
|
+
"handler",
|
|
77
|
+
"discard",
|
|
78
|
+
"foreignObject"
|
|
79
|
+
];
|
|
80
|
+
let result = html;
|
|
81
|
+
dangerousTags.forEach((tag) => {
|
|
82
|
+
const regex = new RegExp(String.raw`<${tag}[^>]*>[\s\S]*?<\/${tag}>|<${tag}[^>]*\/>`, "gi");
|
|
83
|
+
result = result.replaceAll(regex, "");
|
|
84
|
+
});
|
|
85
|
+
return result;
|
|
86
|
+
};
|
|
87
|
+
var isSafeUrl = (url) => {
|
|
88
|
+
const allowedProtocols = ["https:", "http:", "mailto:", "tel:"];
|
|
89
|
+
try {
|
|
90
|
+
const parsed = new URL(url);
|
|
91
|
+
return allowedProtocols.includes(parsed.protocol);
|
|
92
|
+
} catch {
|
|
93
|
+
const low = url.trim().toLowerCase();
|
|
94
|
+
return !low.startsWith("javascript:") && !low.startsWith("//") && (low.startsWith("/") || low.startsWith("."));
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/headers/index.ts
|
|
99
|
+
var headers_exports = {};
|
|
100
|
+
__export(headers_exports, {
|
|
101
|
+
buildCSP: () => buildCSP,
|
|
102
|
+
buildStrictCSP: () => buildStrictCSP,
|
|
103
|
+
generateNonce: () => generateNonce,
|
|
104
|
+
parseCSPReport: () => parseCSPReport,
|
|
105
|
+
secureCSP: () => secureCSP,
|
|
106
|
+
standardHeaders: () => standardHeaders
|
|
107
|
+
});
|
|
108
|
+
import { randomBytes } from "crypto";
|
|
109
|
+
var standardHeaders = {
|
|
110
|
+
"Content-Security-Policy": "default-src 'self'; img-src 'self'; script-src 'self'; style-src 'self'; frame-ancestors 'none'; object-src 'none'",
|
|
111
|
+
"X-Frame-Options": "DENY",
|
|
112
|
+
"X-Content-Type-Options": "nosniff",
|
|
113
|
+
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
114
|
+
"Strict-Transport-Security": "max-age=31536000; includeSubDomains"
|
|
115
|
+
};
|
|
116
|
+
var generateNonce = (length = 16) => {
|
|
117
|
+
return randomBytes(length).toString("base64");
|
|
118
|
+
};
|
|
119
|
+
var buildCSP = (directives, nonce) => {
|
|
120
|
+
return Object.entries(directives).filter((entry) => {
|
|
121
|
+
const sources = entry[1];
|
|
122
|
+
return Array.isArray(sources) && sources.length > 0;
|
|
123
|
+
}).map(([directive, sources]) => {
|
|
124
|
+
const updatedSources = nonce && (directive === "script-src" || directive === "style-src") ? [...sources, `'nonce-${nonce}'`] : sources;
|
|
125
|
+
return `${directive} ${updatedSources.join(" ")}`;
|
|
126
|
+
}).join("; ");
|
|
127
|
+
};
|
|
128
|
+
var buildStrictCSP = (nonce) => {
|
|
129
|
+
return buildCSP(
|
|
130
|
+
{
|
|
131
|
+
"object-src": ["'none'"],
|
|
132
|
+
"script-src": ["'strict-dynamic'", `'nonce-${nonce}'`, "'unsafe-inline'", "http:", "https:"],
|
|
133
|
+
"base-uri": ["'none'"]
|
|
134
|
+
},
|
|
135
|
+
nonce
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
var parseCSPReport = (reportBody) => {
|
|
139
|
+
if (!reportBody) return null;
|
|
140
|
+
const report = reportBody["csp-report"] || (Array.isArray(reportBody) ? reportBody[0] : reportBody);
|
|
141
|
+
if (!report?.["blocked-uri"]) return null;
|
|
142
|
+
return {
|
|
143
|
+
documentUri: report["document-uri"],
|
|
144
|
+
referrer: report["referrer"],
|
|
145
|
+
blockedUri: report["blocked-uri"],
|
|
146
|
+
violatedDirective: report["violated-directive"],
|
|
147
|
+
originalPolicy: report["original-policy"],
|
|
148
|
+
disposition: report["disposition"],
|
|
149
|
+
statusCode: report["status-code"],
|
|
150
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
var secureCSP = buildCSP({
|
|
154
|
+
"default-src": ["'none'"],
|
|
155
|
+
"script-src": ["'self'"],
|
|
156
|
+
"style-src": ["'self'"],
|
|
157
|
+
"img-src": ["'self'"],
|
|
158
|
+
"connect-src": ["'self'"],
|
|
159
|
+
"font-src": ["'self'"],
|
|
160
|
+
"object-src": ["'none'"],
|
|
161
|
+
"base-uri": ["'none'"],
|
|
162
|
+
"form-action": ["'self'"],
|
|
163
|
+
"frame-ancestors": ["'none'"]
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// src/auth/index.ts
|
|
167
|
+
var auth_exports = {};
|
|
168
|
+
__export(auth_exports, {
|
|
169
|
+
basicAuth: () => basicAuth,
|
|
170
|
+
can: () => can,
|
|
171
|
+
canAccess: () => canAccess,
|
|
172
|
+
generateCSRF: () => generateCSRF,
|
|
173
|
+
generateHmacChallenge: () => generateHmacChallenge,
|
|
174
|
+
generateHoneyToken: () => generateHoneyToken,
|
|
175
|
+
generatePasskeyChallenge: () => generatePasskeyChallenge,
|
|
176
|
+
hasRole: () => hasRole,
|
|
177
|
+
isHoneyToken: () => isHoneyToken,
|
|
178
|
+
isJwtValid: () => isJwtValid,
|
|
179
|
+
parseAuthenticatorData: () => parseAuthenticatorData,
|
|
180
|
+
suggestMFA: () => suggestMFA,
|
|
181
|
+
verifyCSRF: () => verifyCSRF,
|
|
182
|
+
verifyHmacResponse: () => verifyHmacResponse,
|
|
183
|
+
verifyPasskeyAssertion: () => verifyPasskeyAssertion,
|
|
184
|
+
verifyPasskeyRegistration: () => verifyPasskeyRegistration,
|
|
185
|
+
verifyPasskeySignature: () => verifyPasskeySignature
|
|
186
|
+
});
|
|
187
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
188
|
+
import { randomBytes as randomBytes2, timingSafeEqual, createHmac, createHash, verify } from "crypto";
|
|
189
|
+
var basicAuth = (user, pass) => {
|
|
190
|
+
const credentials = user + ":" + pass;
|
|
191
|
+
return "Basic " + Buffer2.from(credentials).toString("base64");
|
|
192
|
+
};
|
|
193
|
+
var verifyPasskeySignature = (_challenge, response, signature, publicKey, algorithm = "sha256") => {
|
|
194
|
+
try {
|
|
195
|
+
const data = Buffer2.isBuffer(response) ? response : Buffer2.from(response);
|
|
196
|
+
const sig = Buffer2.isBuffer(signature) ? signature : Buffer2.from(signature, "base64");
|
|
197
|
+
return verify(algorithm, data, publicKey, sig);
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
var suggestMFA = (riskScore, lastFingerprint, currentFingerprint) => {
|
|
203
|
+
if (lastFingerprint !== currentFingerprint) return true;
|
|
204
|
+
if (riskScore > 10) return true;
|
|
205
|
+
return false;
|
|
206
|
+
};
|
|
207
|
+
var generateHmacChallenge = (userSalt) => {
|
|
208
|
+
return createHmac("sha256", userSalt).update(randomBytes2(32)).digest("hex");
|
|
209
|
+
};
|
|
210
|
+
var verifyHmacResponse = (challenge, proof, secret) => {
|
|
211
|
+
const expected = createHmac("sha256", secret).update(challenge).digest("hex");
|
|
212
|
+
const proofBuf = Buffer2.from(proof, "hex");
|
|
213
|
+
const expectedBuf = Buffer2.from(expected, "hex");
|
|
214
|
+
if (proofBuf.length !== expectedBuf.length) return false;
|
|
215
|
+
return timingSafeEqual(proofBuf, expectedBuf);
|
|
216
|
+
};
|
|
217
|
+
var generateHoneyToken = (prefix = "sk") => {
|
|
218
|
+
return `${prefix}_${randomBytes2(16).toString("hex")}_${randomBytes2(8).toString("hex")}`;
|
|
219
|
+
};
|
|
220
|
+
var isHoneyToken = (token, prefix = "sk") => {
|
|
221
|
+
if (!token) return false;
|
|
222
|
+
const parts = token.split("_");
|
|
223
|
+
return parts.length >= 3 && parts[0] === prefix;
|
|
224
|
+
};
|
|
225
|
+
var generateCSRF = () => {
|
|
226
|
+
const token = randomBytes2(32).toString("hex");
|
|
227
|
+
const cookieValue = randomBytes2(32).toString("hex");
|
|
228
|
+
return { token, cookieValue };
|
|
229
|
+
};
|
|
230
|
+
var verifyCSRF = (providedToken, cookieToken) => {
|
|
231
|
+
if (!providedToken || !cookieToken) return false;
|
|
232
|
+
const tokenBuf = Buffer2.from(providedToken, "hex");
|
|
233
|
+
const cookieBuf = Buffer2.from(cookieToken, "hex");
|
|
234
|
+
if (tokenBuf.length !== cookieBuf.length) return false;
|
|
235
|
+
return timingSafeEqual(tokenBuf, cookieBuf);
|
|
236
|
+
};
|
|
237
|
+
var isJwtValid = (token, secretOrPublicKey, algorithm = "HS256") => {
|
|
238
|
+
try {
|
|
239
|
+
const parts = token.split(".");
|
|
240
|
+
if (parts.length !== 3) return false;
|
|
241
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
242
|
+
const header = JSON.parse(Buffer2.from(headerB64, "base64url").toString("utf8"));
|
|
243
|
+
if (!header.alg || header.alg.toLowerCase() === "none" || header.alg !== algorithm)
|
|
244
|
+
return false;
|
|
245
|
+
const payload = JSON.parse(Buffer2.from(payloadB64, "base64url").toString("utf8"));
|
|
246
|
+
if (payload.exp && Date.now() >= payload.exp * 1e3) return false;
|
|
247
|
+
const dataToSign = `${headerB64}.${payloadB64}`;
|
|
248
|
+
const signatureBuffer = Buffer2.from(signatureB64, "base64url");
|
|
249
|
+
if (algorithm.startsWith("HS")) {
|
|
250
|
+
const hashAlg = algorithm.replace("HS", "sha");
|
|
251
|
+
const expectedSignature = createHmac(hashAlg, secretOrPublicKey).update(dataToSign).digest();
|
|
252
|
+
if (signatureBuffer.length !== expectedSignature.length) return false;
|
|
253
|
+
return timingSafeEqual(signatureBuffer, expectedSignature);
|
|
254
|
+
} else if (algorithm.startsWith("RS") || algorithm.startsWith("ES")) {
|
|
255
|
+
const hashAlg = algorithm.replace(/RS|ES/, "sha");
|
|
256
|
+
return verify(hashAlg, Buffer2.from(dataToSign), secretOrPublicKey, signatureBuffer);
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
} catch {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var canAccess = (user, resource, rule) => {
|
|
264
|
+
return rule(user, resource);
|
|
265
|
+
};
|
|
266
|
+
var hasRole = (userRoles, requiredRoles) => {
|
|
267
|
+
return requiredRoles.every((role) => userRoles.includes(role));
|
|
268
|
+
};
|
|
269
|
+
var can = (userPermissions, requiredPermission) => {
|
|
270
|
+
return userPermissions.includes(requiredPermission);
|
|
271
|
+
};
|
|
272
|
+
var generatePasskeyChallenge = (length = 32) => {
|
|
273
|
+
return randomBytes2(length).toString("base64url");
|
|
274
|
+
};
|
|
275
|
+
var verifyPasskeyRegistration = (clientDataJSONStr, expectedChallenge, expectedOrigin) => {
|
|
276
|
+
try {
|
|
277
|
+
let jsonStr = clientDataJSONStr;
|
|
278
|
+
try {
|
|
279
|
+
JSON.parse(clientDataJSONStr);
|
|
280
|
+
} catch {
|
|
281
|
+
jsonStr = Buffer2.from(clientDataJSONStr, "base64url").toString("utf8");
|
|
282
|
+
}
|
|
283
|
+
const clientData = JSON.parse(jsonStr);
|
|
284
|
+
const receivedBytes = Buffer2.from(clientData.challenge ?? "", "base64url");
|
|
285
|
+
const expectedBytes = Buffer2.from(expectedChallenge, "base64url");
|
|
286
|
+
const challengeMatches = receivedBytes.length === expectedBytes.length && receivedBytes.length > 0 && timingSafeEqual(receivedBytes, expectedBytes);
|
|
287
|
+
const typeMatches = clientData.type === "webauthn.create";
|
|
288
|
+
let originMatches = true;
|
|
289
|
+
if (expectedOrigin && clientData.origin !== expectedOrigin) {
|
|
290
|
+
originMatches = false;
|
|
291
|
+
}
|
|
292
|
+
if (!challengeMatches) return { verified: false, error: "Challenge mismatch." };
|
|
293
|
+
if (!typeMatches) return { verified: false, error: "Invalid type (expected webauthn.create)." };
|
|
294
|
+
if (!originMatches) return { verified: false, error: "Origin mismatch." };
|
|
295
|
+
return { verified: true };
|
|
296
|
+
} catch (err) {
|
|
297
|
+
return { verified: false, error: err.message };
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
var parseAuthenticatorData = (authData) => {
|
|
301
|
+
try {
|
|
302
|
+
const rpIdHash = authData.subarray(0, 32);
|
|
303
|
+
const flags = authData[32];
|
|
304
|
+
const signCount = authData.readUInt32BE(33);
|
|
305
|
+
const attestedCredentialDataPresent = !!(flags & 64);
|
|
306
|
+
if (!attestedCredentialDataPresent) {
|
|
307
|
+
return { rpIdHash, flags, signCount };
|
|
308
|
+
}
|
|
309
|
+
const aaguid = authData.subarray(37, 53);
|
|
310
|
+
const credentialIdLength = authData.readUInt16BE(53);
|
|
311
|
+
const credentialId = authData.subarray(55, 55 + credentialIdLength);
|
|
312
|
+
const publicKeyBytes = authData.subarray(55 + credentialIdLength);
|
|
313
|
+
return {
|
|
314
|
+
rpIdHash,
|
|
315
|
+
flags,
|
|
316
|
+
signCount,
|
|
317
|
+
aaguid,
|
|
318
|
+
credentialId: credentialId.toString("base64url"),
|
|
319
|
+
publicKeyBytes: publicKeyBytes.toString("hex")
|
|
320
|
+
};
|
|
321
|
+
} catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
var verifyPasskeyAssertion = (clientDataJSONStr, expectedChallenge, expectedOrigin, storedSignCount, authData, signature, publicKeyPem) => {
|
|
326
|
+
try {
|
|
327
|
+
let jsonStr = clientDataJSONStr;
|
|
328
|
+
try {
|
|
329
|
+
JSON.parse(clientDataJSONStr);
|
|
330
|
+
} catch {
|
|
331
|
+
jsonStr = Buffer2.from(clientDataJSONStr, "base64url").toString("utf8");
|
|
332
|
+
}
|
|
333
|
+
const clientData = JSON.parse(jsonStr);
|
|
334
|
+
if (clientData.type !== "webauthn.get") {
|
|
335
|
+
return { verified: false, error: "Invalid type (expected webauthn.get)." };
|
|
336
|
+
}
|
|
337
|
+
const receivedBytes = Buffer2.from(clientData.challenge ?? "", "base64url");
|
|
338
|
+
const expectedBytes = Buffer2.from(expectedChallenge, "base64url");
|
|
339
|
+
const challengeMatches = receivedBytes.length === expectedBytes.length && receivedBytes.length > 0 && timingSafeEqual(receivedBytes, expectedBytes);
|
|
340
|
+
if (!challengeMatches) return { verified: false, error: "Challenge mismatch." };
|
|
341
|
+
if (clientData.origin !== expectedOrigin) {
|
|
342
|
+
return { verified: false, error: "Origin mismatch." };
|
|
343
|
+
}
|
|
344
|
+
const newSignCount = authData.readUInt32BE(33);
|
|
345
|
+
if (storedSignCount !== 0 && newSignCount <= storedSignCount) {
|
|
346
|
+
return {
|
|
347
|
+
verified: false,
|
|
348
|
+
error: `Replay attack or cloned authenticator detected: signCount ${newSignCount} is not greater than stored ${storedSignCount}.`
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
const clientDataHash = createHash("sha256").update(Buffer2.from(jsonStr, "utf8")).digest();
|
|
352
|
+
const signedData = Buffer2.concat([authData, clientDataHash]);
|
|
353
|
+
const sigValid = verify("sha256", signedData, publicKeyPem, signature);
|
|
354
|
+
if (!sigValid) return { verified: false, error: "Signature verification failed." };
|
|
355
|
+
return { verified: true, newSignCount };
|
|
356
|
+
} catch (err) {
|
|
357
|
+
return { verified: false, error: err.message };
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/crypto/index.ts
|
|
362
|
+
var crypto_exports = {};
|
|
363
|
+
__export(crypto_exports, {
|
|
364
|
+
blindData: () => blindData,
|
|
365
|
+
decryptSession: () => decryptSession,
|
|
366
|
+
deriveSubKey: () => deriveSubKey,
|
|
367
|
+
encryptSession: () => encryptSession,
|
|
368
|
+
generatePQCKeyPair: () => generatePQCKeyPair,
|
|
369
|
+
generateToken: () => generateToken,
|
|
370
|
+
hashPassword: () => hashPassword,
|
|
371
|
+
reconstructSecret: () => reconstructSecret,
|
|
372
|
+
runIsolatedCrypto: () => runIsolatedCrypto,
|
|
373
|
+
sha256: () => sha256,
|
|
374
|
+
signPQC: () => signPQC,
|
|
375
|
+
splitSecret: () => splitSecret,
|
|
376
|
+
verifyPQCSignature: () => verifyPQCSignature,
|
|
377
|
+
verifyPassword: () => verifyPassword
|
|
378
|
+
});
|
|
379
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
380
|
+
import {
|
|
381
|
+
randomBytes as randomBytes3,
|
|
382
|
+
createHash as createHash2,
|
|
383
|
+
scrypt,
|
|
384
|
+
timingSafeEqual as timingSafeEqual2,
|
|
385
|
+
createHmac as createHmac2,
|
|
386
|
+
createCipheriv,
|
|
387
|
+
createDecipheriv,
|
|
388
|
+
hkdfSync
|
|
389
|
+
} from "crypto";
|
|
390
|
+
import { promisify } from "util";
|
|
391
|
+
import { fileURLToPath } from "url";
|
|
392
|
+
import { cpus } from "os";
|
|
393
|
+
|
|
394
|
+
// src/utils/telemetry.ts
|
|
395
|
+
var telemetry_exports = {};
|
|
396
|
+
__export(telemetry_exports, {
|
|
397
|
+
getTelemetryProvider: () => getTelemetryProvider,
|
|
398
|
+
resetTelemetryProvider: () => resetTelemetryProvider,
|
|
399
|
+
setTelemetryProvider: () => setTelemetryProvider
|
|
400
|
+
});
|
|
401
|
+
var NOOP_PROVIDER = {
|
|
402
|
+
onRateLimitBlocked: () => {
|
|
403
|
+
},
|
|
404
|
+
onSecurityEvent: () => {
|
|
405
|
+
},
|
|
406
|
+
onKillSwitchTriggered: () => {
|
|
407
|
+
},
|
|
408
|
+
onWorkerSpawned: () => {
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
var _provider = NOOP_PROVIDER;
|
|
412
|
+
var setTelemetryProvider = (provider) => {
|
|
413
|
+
_provider = provider;
|
|
414
|
+
};
|
|
415
|
+
var resetTelemetryProvider = () => {
|
|
416
|
+
_provider = NOOP_PROVIDER;
|
|
417
|
+
};
|
|
418
|
+
var getTelemetryProvider = () => _provider;
|
|
419
|
+
|
|
420
|
+
// src/crypto/index.ts
|
|
421
|
+
var scryptAsync = promisify(scrypt);
|
|
422
|
+
var getFilename = () => {
|
|
423
|
+
return fileURLToPath(import.meta.url);
|
|
424
|
+
};
|
|
425
|
+
var BoundedWorkerSemaphore = class {
|
|
426
|
+
constructor(maxConcurrent = Math.max(2, cpus().length - 1)) {
|
|
427
|
+
this.maxConcurrent = maxConcurrent;
|
|
428
|
+
}
|
|
429
|
+
maxConcurrent;
|
|
430
|
+
active = 0;
|
|
431
|
+
queue = [];
|
|
432
|
+
async run(task) {
|
|
433
|
+
if (this.active >= this.maxConcurrent) {
|
|
434
|
+
await new Promise((resolve2) => this.queue.push(resolve2));
|
|
435
|
+
}
|
|
436
|
+
this.active++;
|
|
437
|
+
try {
|
|
438
|
+
return await task();
|
|
439
|
+
} finally {
|
|
440
|
+
this.active--;
|
|
441
|
+
const next = this.queue.shift();
|
|
442
|
+
if (next) next();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
var workerPool = new BoundedWorkerSemaphore();
|
|
447
|
+
var runIsolatedCrypto = async (functionName, args) => {
|
|
448
|
+
return workerPool.run(async () => {
|
|
449
|
+
getTelemetryProvider().onWorkerSpawned?.(functionName);
|
|
450
|
+
const { Worker } = await import("worker_threads");
|
|
451
|
+
return new Promise((resolve2, reject) => {
|
|
452
|
+
const worker = new Worker(getFilename(), {
|
|
453
|
+
workerData: { functionName, args }
|
|
454
|
+
});
|
|
455
|
+
worker.on("message", (msg) => {
|
|
456
|
+
if (msg && msg.status === "success") {
|
|
457
|
+
resolve2(msg.result !== void 0 ? msg.result : msg);
|
|
458
|
+
} else {
|
|
459
|
+
reject(new Error(msg?.error || "Worker execution failed."));
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
worker.on("error", reject);
|
|
463
|
+
worker.on("exit", (code) => {
|
|
464
|
+
if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`));
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
};
|
|
469
|
+
var gfExp = new Uint8Array(256);
|
|
470
|
+
var gfLog = new Uint8Array(256);
|
|
471
|
+
var gfX = 1;
|
|
472
|
+
for (let i = 0; i < 255; i++) {
|
|
473
|
+
gfExp[i] = gfX;
|
|
474
|
+
gfLog[gfX] = i;
|
|
475
|
+
gfX = gfX << 1;
|
|
476
|
+
if (gfX & 256) {
|
|
477
|
+
gfX = gfX ^ 285;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
gfExp[255] = gfExp[0];
|
|
481
|
+
var gfMul = (a, b) => {
|
|
482
|
+
if (a === 0 || b === 0) return 0;
|
|
483
|
+
return gfExp[(gfLog[a] + gfLog[b]) % 255];
|
|
484
|
+
};
|
|
485
|
+
var gfDiv = (a, b) => {
|
|
486
|
+
if (a === 0) return 0;
|
|
487
|
+
if (b === 0) throw new Error("Division by zero in GF(256)");
|
|
488
|
+
return gfExp[(gfLog[a] - gfLog[b] + 255) % 255];
|
|
489
|
+
};
|
|
490
|
+
var evaluatePolynomial = (coefficients, x) => {
|
|
491
|
+
let result = 0;
|
|
492
|
+
let power = 1;
|
|
493
|
+
for (const coeff of coefficients) {
|
|
494
|
+
result ^= gfMul(coeff, power);
|
|
495
|
+
power = gfMul(power, x);
|
|
496
|
+
}
|
|
497
|
+
return result;
|
|
498
|
+
};
|
|
499
|
+
var splitSecret = (secret, sharesCount, threshold) => {
|
|
500
|
+
if (threshold > sharesCount) throw new Error("Threshold cannot be greater than sharesCount.");
|
|
501
|
+
if (threshold < 1) throw new Error("Threshold must be at least 1.");
|
|
502
|
+
if (sharesCount > 255)
|
|
503
|
+
throw new Error("Shamir Secret Sharing GF(256) supports up to 255 shares.");
|
|
504
|
+
const secretBuffer = Buffer3.isBuffer(secret) ? secret : Buffer3.from(secret);
|
|
505
|
+
const secretLen = secretBuffer.length;
|
|
506
|
+
const shareBuffers = Array.from({ length: sharesCount }, () => Buffer3.alloc(secretLen));
|
|
507
|
+
for (let byteIdx = 0; byteIdx < secretLen; byteIdx++) {
|
|
508
|
+
const s = secretBuffer[byteIdx];
|
|
509
|
+
const coefficients = [s];
|
|
510
|
+
for (let k = 1; k < threshold; k++) {
|
|
511
|
+
coefficients.push(randomBytes3(1)[0]);
|
|
512
|
+
}
|
|
513
|
+
for (let i = 0; i < sharesCount; i++) {
|
|
514
|
+
shareBuffers[i][byteIdx] = evaluatePolynomial(coefficients, i + 1);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return shareBuffers.map((buf, i) => `${i + 1}:${buf.toString("hex")}`);
|
|
518
|
+
};
|
|
519
|
+
var reconstructSecret = (shares) => {
|
|
520
|
+
if (shares.length === 0) throw new Error("No shares provided.");
|
|
521
|
+
const parsedShares = shares.map((s) => {
|
|
522
|
+
const [xStr, hex] = s.split(":");
|
|
523
|
+
return {
|
|
524
|
+
x: Number.parseInt(xStr, 10),
|
|
525
|
+
y: Buffer3.from(hex, "hex")
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
const secretLen = parsedShares[0].y.length;
|
|
529
|
+
const secretBuffer = Buffer3.alloc(secretLen);
|
|
530
|
+
for (let byteIdx = 0; byteIdx < secretLen; byteIdx++) {
|
|
531
|
+
let secretByte = 0;
|
|
532
|
+
for (let i = 0; i < parsedShares.length; i++) {
|
|
533
|
+
let li = 1;
|
|
534
|
+
for (let j = 0; j < parsedShares.length; j++) {
|
|
535
|
+
if (i === j) continue;
|
|
536
|
+
const num = parsedShares[j].x;
|
|
537
|
+
const denom = parsedShares[i].x ^ parsedShares[j].x;
|
|
538
|
+
li = gfMul(li, gfDiv(num, denom));
|
|
539
|
+
}
|
|
540
|
+
secretByte = secretByte ^ gfMul(parsedShares[i].y[byteIdx], li);
|
|
541
|
+
}
|
|
542
|
+
secretBuffer[byteIdx] = secretByte;
|
|
543
|
+
}
|
|
544
|
+
return secretBuffer;
|
|
545
|
+
};
|
|
546
|
+
var blindData = (data, factor) => {
|
|
547
|
+
return createHmac2("sha256", factor).update(data).digest("hex");
|
|
548
|
+
};
|
|
549
|
+
var generateToken = (length = 32, encoding = "hex") => {
|
|
550
|
+
return randomBytes3(length).toString(encoding);
|
|
551
|
+
};
|
|
552
|
+
var deriveSubKey = (secret, info, length = 32) => {
|
|
553
|
+
const ikm = Buffer3.isBuffer(secret) ? secret : Buffer3.from(secret);
|
|
554
|
+
const derived = hkdfSync("sha256", ikm, Buffer3.alloc(0), Buffer3.from(info), length);
|
|
555
|
+
return Buffer3.from(derived).toString("hex");
|
|
556
|
+
};
|
|
557
|
+
var encryptSession = (obj, key) => {
|
|
558
|
+
const iv = randomBytes3(12);
|
|
559
|
+
const keyBuffer = Buffer3.isBuffer(key) ? key : Buffer3.from(key, "hex");
|
|
560
|
+
const cipher = createCipheriv("aes-256-gcm", keyBuffer, iv);
|
|
561
|
+
const content = JSON.stringify(obj);
|
|
562
|
+
let encrypted = cipher.update(content, "utf8", "hex");
|
|
563
|
+
encrypted += cipher.final("hex");
|
|
564
|
+
const authTag = cipher.getAuthTag().toString("hex");
|
|
565
|
+
return `${iv.toString("hex")}:${authTag}:${encrypted}`;
|
|
566
|
+
};
|
|
567
|
+
var decryptSession = (sessionStr, key) => {
|
|
568
|
+
try {
|
|
569
|
+
const [ivHex, authTagHex, encrypted] = sessionStr.split(":");
|
|
570
|
+
const keyBuffer = Buffer3.isBuffer(key) ? key : Buffer3.from(key, "hex");
|
|
571
|
+
const iv = Buffer3.from(ivHex, "hex");
|
|
572
|
+
const authTag = Buffer3.from(authTagHex, "hex");
|
|
573
|
+
const decipher = createDecipheriv("aes-256-gcm", keyBuffer, iv);
|
|
574
|
+
decipher.setAuthTag(authTag);
|
|
575
|
+
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
576
|
+
decrypted += decipher.final("utf8");
|
|
577
|
+
return JSON.parse(decrypted);
|
|
578
|
+
} catch {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
var WOTS_LEN = 67;
|
|
583
|
+
var _usedWotsKeys = /* @__PURE__ */ new Set();
|
|
584
|
+
var generatePQCKeyPair = () => {
|
|
585
|
+
const privKeyParts = [];
|
|
586
|
+
const pubKeyParts = [];
|
|
587
|
+
for (let i = 0; i < WOTS_LEN; i++) {
|
|
588
|
+
const part = randomBytes3(32).toString("hex");
|
|
589
|
+
privKeyParts.push(part);
|
|
590
|
+
let current = Buffer3.from(part, "hex");
|
|
591
|
+
for (let j = 0; j < 15; j++) {
|
|
592
|
+
current = createHash2("sha256").update(current).digest();
|
|
593
|
+
}
|
|
594
|
+
pubKeyParts.push(current.toString("hex"));
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
publicKey: pubKeyParts.join(";"),
|
|
598
|
+
privateKey: privKeyParts.join(";")
|
|
599
|
+
};
|
|
600
|
+
};
|
|
601
|
+
var signPQC = (data, privateKeyStr) => {
|
|
602
|
+
const msgHash = createHash2("sha256").update(data).digest();
|
|
603
|
+
const nibbles = [];
|
|
604
|
+
let checksum = 0;
|
|
605
|
+
for (let i = 0; i < 32; i++) {
|
|
606
|
+
const byte = msgHash[i];
|
|
607
|
+
const n1 = byte >> 4;
|
|
608
|
+
const n2 = byte & 15;
|
|
609
|
+
nibbles.push(n1, n2);
|
|
610
|
+
checksum += 15 - n1 + (15 - n2);
|
|
611
|
+
}
|
|
612
|
+
const c1 = checksum >> 8 & 15;
|
|
613
|
+
const c2 = checksum >> 4 & 15;
|
|
614
|
+
const c3 = checksum & 15;
|
|
615
|
+
nibbles.push(c1, c2, c3);
|
|
616
|
+
const privParts = privateKeyStr.split(";");
|
|
617
|
+
if (privParts.length !== WOTS_LEN) throw new Error("Invalid private key length.");
|
|
618
|
+
const keyFingerprint = createHash2("sha256").update(privParts[0]).digest("hex");
|
|
619
|
+
if (_usedWotsKeys.has(keyFingerprint)) {
|
|
620
|
+
throw new Error(
|
|
621
|
+
"WOTS_KEY_REUSE: This WOTS private key has already been used to sign a message. Reusing a WOTS key leaks private key material and breaks the security of the scheme. Generate a new key pair with generatePQCKeyPair() for each message."
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
_usedWotsKeys.add(keyFingerprint);
|
|
625
|
+
const sigParts = [];
|
|
626
|
+
for (let i = 0; i < WOTS_LEN; i++) {
|
|
627
|
+
const n = nibbles[i];
|
|
628
|
+
let current = Buffer3.from(privParts[i], "hex");
|
|
629
|
+
for (let j = 0; j < n; j++) {
|
|
630
|
+
current = createHash2("sha256").update(current).digest();
|
|
631
|
+
}
|
|
632
|
+
sigParts.push(current.toString("hex"));
|
|
633
|
+
}
|
|
634
|
+
return sigParts.join(";");
|
|
635
|
+
};
|
|
636
|
+
var verifyPQCSignature = (data, signature, publicKey) => {
|
|
637
|
+
try {
|
|
638
|
+
const msgHash = createHash2("sha256").update(data).digest();
|
|
639
|
+
const nibbles = [];
|
|
640
|
+
let checksum = 0;
|
|
641
|
+
for (let i = 0; i < 32; i++) {
|
|
642
|
+
const byte = msgHash[i];
|
|
643
|
+
const n1 = byte >> 4;
|
|
644
|
+
const n2 = byte & 15;
|
|
645
|
+
nibbles.push(n1, n2);
|
|
646
|
+
checksum += 15 - n1 + (15 - n2);
|
|
647
|
+
}
|
|
648
|
+
const c1 = checksum >> 8 & 15;
|
|
649
|
+
const c2 = checksum >> 4 & 15;
|
|
650
|
+
const c3 = checksum & 15;
|
|
651
|
+
nibbles.push(c1, c2, c3);
|
|
652
|
+
const sigParts = signature.split(";");
|
|
653
|
+
const pubParts = publicKey.split(";");
|
|
654
|
+
if (sigParts.length !== WOTS_LEN || pubParts.length !== WOTS_LEN) return false;
|
|
655
|
+
for (let i = 0; i < WOTS_LEN; i++) {
|
|
656
|
+
const n = nibbles[i];
|
|
657
|
+
let current = Buffer3.from(sigParts[i], "hex");
|
|
658
|
+
for (let j = 0; j < 15 - n; j++) {
|
|
659
|
+
current = createHash2("sha256").update(current).digest();
|
|
660
|
+
}
|
|
661
|
+
const expected = Buffer3.from(pubParts[i], "hex");
|
|
662
|
+
if (!timingSafeEqual2(current, expected)) return false;
|
|
663
|
+
}
|
|
664
|
+
return true;
|
|
665
|
+
} catch {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
var hashPassword = async (password, salt) => {
|
|
670
|
+
const actualSalt = salt || generateToken(16);
|
|
671
|
+
const derivedKey = await scryptAsync(password, actualSalt, 64);
|
|
672
|
+
return { hash: derivedKey.toString("hex"), salt: actualSalt };
|
|
673
|
+
};
|
|
674
|
+
var verifyPassword = async (password, storedHash, salt) => {
|
|
675
|
+
const { hash } = await hashPassword(password, salt);
|
|
676
|
+
const hashBuffer = Buffer3.from(hash, "hex");
|
|
677
|
+
const storedBuffer = Buffer3.from(storedHash, "hex");
|
|
678
|
+
if (hashBuffer.length !== storedBuffer.length) return false;
|
|
679
|
+
return timingSafeEqual2(hashBuffer, storedBuffer);
|
|
680
|
+
};
|
|
681
|
+
var sha256 = (data) => {
|
|
682
|
+
return createHash2("sha256").update(data).digest("hex");
|
|
683
|
+
};
|
|
684
|
+
var startWorkerIfChild = async () => {
|
|
685
|
+
try {
|
|
686
|
+
const { isMainThread, parentPort, workerData } = await import("worker_threads");
|
|
687
|
+
if (!isMainThread && parentPort) {
|
|
688
|
+
try {
|
|
689
|
+
const { functionName, args = [] } = workerData || {};
|
|
690
|
+
if (functionName === "hashPassword") {
|
|
691
|
+
const [password, salt] = args;
|
|
692
|
+
if (password === void 0) {
|
|
693
|
+
parentPort.postMessage({ status: "success" });
|
|
694
|
+
} else {
|
|
695
|
+
hashPassword(password, salt).then((result) => {
|
|
696
|
+
parentPort.postMessage({ status: "success", result });
|
|
697
|
+
}).catch((err) => {
|
|
698
|
+
parentPort.postMessage({ status: "error", error: err.message });
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
} else if (functionName === "splitSecret") {
|
|
702
|
+
const [secret, sharesCount, threshold] = args;
|
|
703
|
+
const result = splitSecret(secret, sharesCount, threshold);
|
|
704
|
+
parentPort.postMessage({ status: "success", result });
|
|
705
|
+
} else if (functionName === "reconstructSecret") {
|
|
706
|
+
const [shares] = args;
|
|
707
|
+
const result = reconstructSecret(shares);
|
|
708
|
+
parentPort.postMessage({ status: "success", result: result.toString("hex") });
|
|
709
|
+
} else {
|
|
710
|
+
parentPort.postMessage({ status: "error", error: `Unknown function: ${functionName}` });
|
|
711
|
+
}
|
|
712
|
+
} catch (err) {
|
|
713
|
+
parentPort.postMessage({ status: "error", error: err.message });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
startWorkerIfChild();
|
|
720
|
+
|
|
721
|
+
// src/fs/index.ts
|
|
722
|
+
var fs_exports = {};
|
|
723
|
+
__export(fs_exports, {
|
|
724
|
+
MAGIC_NUMBERS: () => MAGIC_NUMBERS,
|
|
725
|
+
isPathSafe: () => isPathSafe,
|
|
726
|
+
resolveSafePath: () => resolveSafePath,
|
|
727
|
+
safeRead: () => safeRead,
|
|
728
|
+
verifyMagicNumber: () => verifyMagicNumber
|
|
729
|
+
});
|
|
730
|
+
import { resolve, relative, isAbsolute } from "path";
|
|
731
|
+
var MAGIC_NUMBERS = {
|
|
732
|
+
PNG: [137, 80, 78, 71],
|
|
733
|
+
JPEG: [255, 216, 255],
|
|
734
|
+
PDF: [37, 80, 68, 70],
|
|
735
|
+
GIF: [71, 73, 70, 56]
|
|
736
|
+
};
|
|
737
|
+
var resolveSafePath = (rootDir, userInputPath) => {
|
|
738
|
+
const absoluteRoot = resolve(rootDir);
|
|
739
|
+
const resolvedPath = resolve(absoluteRoot, userInputPath);
|
|
740
|
+
const relativePath = relative(absoluteRoot, resolvedPath);
|
|
741
|
+
if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
742
|
+
throw new Error("Security Error: Path traversal attempt detected.");
|
|
743
|
+
}
|
|
744
|
+
return resolvedPath;
|
|
745
|
+
};
|
|
746
|
+
var verifyMagicNumber = async (filePath, expectedMagic) => {
|
|
747
|
+
const fs = await import("fs/promises");
|
|
748
|
+
const handle = await fs.open(filePath, "r");
|
|
749
|
+
try {
|
|
750
|
+
const buffer = Buffer.alloc(expectedMagic.length);
|
|
751
|
+
await handle.read(buffer, 0, expectedMagic.length, 0);
|
|
752
|
+
return expectedMagic.every((byte, index) => buffer[index] === byte);
|
|
753
|
+
} finally {
|
|
754
|
+
await handle.close();
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
var safeRead = async (rootDir, filePath) => {
|
|
758
|
+
const fs = await import("fs/promises");
|
|
759
|
+
const safePath = resolveSafePath(rootDir, filePath);
|
|
760
|
+
return fs.readFile(safePath, "utf8");
|
|
761
|
+
};
|
|
762
|
+
var isPathSafe = (rootDir, filePath) => {
|
|
763
|
+
try {
|
|
764
|
+
resolveSafePath(rootDir, filePath);
|
|
765
|
+
return true;
|
|
766
|
+
} catch {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// src/http/index.ts
|
|
772
|
+
var http_exports = {};
|
|
773
|
+
__export(http_exports, {
|
|
774
|
+
analyzeDoSThreat: () => analyzeDoSThreat,
|
|
775
|
+
generateFingerprint: () => generateFingerprint,
|
|
776
|
+
generateSRI: () => generateSRI,
|
|
777
|
+
generateTLSFingerprint: () => generateTLSFingerprint,
|
|
778
|
+
getHardenedRequestOptions: () => getHardenedRequestOptions,
|
|
779
|
+
isSafeRedirect: () => isSafeRedirect,
|
|
780
|
+
isSecure: () => isSecure,
|
|
781
|
+
normalizeUrl: () => normalizeUrl,
|
|
782
|
+
projectNoise: () => projectNoise
|
|
783
|
+
});
|
|
784
|
+
import { createHash as createHash3, randomInt, randomBytes as randomBytes4 } from "crypto";
|
|
785
|
+
var isSecure = (url, headers = {}) => {
|
|
786
|
+
if (url?.startsWith("https://")) return true;
|
|
787
|
+
const proto = headers["x-forwarded-proto"];
|
|
788
|
+
if (typeof proto === "string" && proto.toLowerCase() === "https") return true;
|
|
789
|
+
return false;
|
|
790
|
+
};
|
|
791
|
+
var normalizeUrl = (url) => {
|
|
792
|
+
try {
|
|
793
|
+
const parsed = new URL(url);
|
|
794
|
+
parsed.pathname = parsed.pathname.replaceAll("//", "/");
|
|
795
|
+
return parsed.toString();
|
|
796
|
+
} catch {
|
|
797
|
+
return url.replaceAll("//", "/");
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
var getHardenedRequestOptions = (options = {}) => {
|
|
801
|
+
return {
|
|
802
|
+
...options,
|
|
803
|
+
minVersion: "TLSv1.3",
|
|
804
|
+
ciphers: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256",
|
|
805
|
+
rejectUnauthorized: true
|
|
806
|
+
};
|
|
807
|
+
};
|
|
808
|
+
var generateTLSFingerprint = (req) => {
|
|
809
|
+
const headers = req.headers || {};
|
|
810
|
+
const fingerprintParts = [
|
|
811
|
+
headers["user-agent"] || "",
|
|
812
|
+
headers["accept"] || "",
|
|
813
|
+
headers["accept-language"] || "",
|
|
814
|
+
headers["accept-encoding"] || "",
|
|
815
|
+
headers["connection"] || "",
|
|
816
|
+
headers["upgrade-insecure-requests"] || ""
|
|
817
|
+
];
|
|
818
|
+
return createHash3("sha256").update(fingerprintParts.join("|")).digest("hex");
|
|
819
|
+
};
|
|
820
|
+
var analyzeDoSThreat = (req) => {
|
|
821
|
+
const headers = req.headers || {};
|
|
822
|
+
if (headers["transfer-encoding"] && headers["content-length"]) return true;
|
|
823
|
+
if (headers["connection"] === "keep-alive" && !headers["content-length"] && req.method === "POST")
|
|
824
|
+
return true;
|
|
825
|
+
return false;
|
|
826
|
+
};
|
|
827
|
+
var generateFingerprint = (reqHeaders, ip) => {
|
|
828
|
+
const parts = [
|
|
829
|
+
ip,
|
|
830
|
+
reqHeaders["user-agent"] || "unknown",
|
|
831
|
+
reqHeaders["accept-language"] || "unknown",
|
|
832
|
+
reqHeaders["accept-encoding"] || "unknown",
|
|
833
|
+
reqHeaders["sec-ch-ua"] || "unknown",
|
|
834
|
+
reqHeaders["sec-ch-ua-platform"] || "unknown"
|
|
835
|
+
];
|
|
836
|
+
return createHash3("sha256").update(parts.join("|")).digest("hex");
|
|
837
|
+
};
|
|
838
|
+
var projectNoise = (obj) => {
|
|
839
|
+
if (typeof obj !== "object" || obj === null) return obj;
|
|
840
|
+
const noiseCount = randomInt(1, 4);
|
|
841
|
+
const result = { ...obj };
|
|
842
|
+
for (let i = 0; i < noiseCount; i++) {
|
|
843
|
+
const noiseKey = `_sk_${randomBytes4(4).toString("hex")}`;
|
|
844
|
+
const noiseValue = randomBytes4(8).toString("base64");
|
|
845
|
+
result[noiseKey] = noiseValue;
|
|
846
|
+
}
|
|
847
|
+
return result;
|
|
848
|
+
};
|
|
849
|
+
var isSafeRedirect = (url, allowedHosts = []) => {
|
|
850
|
+
if (url.startsWith("/")) return !url.startsWith("//");
|
|
851
|
+
try {
|
|
852
|
+
const parsed = new URL(url);
|
|
853
|
+
return allowedHosts.includes(parsed.hostname);
|
|
854
|
+
} catch {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
var generateSRI = (data, algorithm = "sha384") => {
|
|
859
|
+
const hash = createHash3(algorithm).update(data).digest("base64");
|
|
860
|
+
return `${algorithm}-${hash}`;
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
// src/rate-limit/index.ts
|
|
864
|
+
var rate_limit_exports = {};
|
|
865
|
+
__export(rate_limit_exports, {
|
|
866
|
+
AdaptiveRateLimiter: () => AdaptiveRateLimiter,
|
|
867
|
+
MemoryStore: () => MemoryStore,
|
|
868
|
+
RateLimiter: () => RateLimiter,
|
|
869
|
+
RedisStore: () => RedisStore,
|
|
870
|
+
TokenBucket: () => TokenBucket
|
|
871
|
+
});
|
|
872
|
+
var MemoryStore = class {
|
|
873
|
+
constructor(windowMs) {
|
|
874
|
+
this.windowMs = windowMs;
|
|
875
|
+
this.gcInterval = setInterval(() => this.gc(), 6e4);
|
|
876
|
+
if (this.gcInterval.unref) {
|
|
877
|
+
this.gcInterval.unref();
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
windowMs;
|
|
881
|
+
hits = /* @__PURE__ */ new Map();
|
|
882
|
+
gcInterval;
|
|
883
|
+
gc() {
|
|
884
|
+
const now = Date.now();
|
|
885
|
+
for (const [key, hit] of this.hits.entries()) {
|
|
886
|
+
if (now > hit.expires) {
|
|
887
|
+
this.hits.delete(key);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
async increment(key, amount = 1) {
|
|
892
|
+
const now = Date.now();
|
|
893
|
+
const hit = this.hits.get(key);
|
|
894
|
+
if (!hit || now > hit.expires) {
|
|
895
|
+
this.hits.set(key, { count: amount, expires: now + this.windowMs });
|
|
896
|
+
return amount;
|
|
897
|
+
}
|
|
898
|
+
hit.count += amount;
|
|
899
|
+
return hit.count;
|
|
900
|
+
}
|
|
901
|
+
async get(key) {
|
|
902
|
+
const hit = this.hits.get(key);
|
|
903
|
+
if (!hit || Date.now() > hit.expires) return void 0;
|
|
904
|
+
return hit.count;
|
|
905
|
+
}
|
|
906
|
+
async reset(key) {
|
|
907
|
+
this.hits.delete(key);
|
|
908
|
+
}
|
|
909
|
+
async setTTL(key, value, ttlMs) {
|
|
910
|
+
this.hits.set(key, { count: value, expires: Date.now() + ttlMs });
|
|
911
|
+
}
|
|
912
|
+
// Cleanup method if needed
|
|
913
|
+
destroy() {
|
|
914
|
+
clearInterval(this.gcInterval);
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
var RedisStore = class {
|
|
918
|
+
constructor(redisClient, windowMs, prefix = "rl:") {
|
|
919
|
+
this.redisClient = redisClient;
|
|
920
|
+
this.windowMs = windowMs;
|
|
921
|
+
this.prefix = prefix;
|
|
922
|
+
}
|
|
923
|
+
redisClient;
|
|
924
|
+
windowMs;
|
|
925
|
+
prefix;
|
|
926
|
+
async increment(key, amount = 1) {
|
|
927
|
+
const prefixedKey = this.prefix + key;
|
|
928
|
+
const multi = this.redisClient.multi();
|
|
929
|
+
multi.incrby(prefixedKey, amount);
|
|
930
|
+
multi.pttl(prefixedKey);
|
|
931
|
+
const results = await multi.exec();
|
|
932
|
+
const count = Number.parseInt(results[0][1], 10);
|
|
933
|
+
const ttl = Number.parseInt(results[1][1], 10);
|
|
934
|
+
if (ttl === -1) {
|
|
935
|
+
await this.redisClient.pexpire(prefixedKey, this.windowMs);
|
|
936
|
+
}
|
|
937
|
+
return count;
|
|
938
|
+
}
|
|
939
|
+
async get(key) {
|
|
940
|
+
const val = await this.redisClient.get(this.prefix + key);
|
|
941
|
+
return val ? Number.parseInt(val, 10) : void 0;
|
|
942
|
+
}
|
|
943
|
+
async reset(key) {
|
|
944
|
+
await this.redisClient.del(this.prefix + key);
|
|
945
|
+
}
|
|
946
|
+
async setTTL(key, value, ttlMs) {
|
|
947
|
+
const prefixedKey = this.prefix + key;
|
|
948
|
+
await this.redisClient.set(prefixedKey, value, "PX", ttlMs);
|
|
949
|
+
}
|
|
950
|
+
};
|
|
951
|
+
var TokenBucket = class {
|
|
952
|
+
constructor(capacity, refillRatePerSecond) {
|
|
953
|
+
this.capacity = capacity;
|
|
954
|
+
this.refillRatePerSecond = refillRatePerSecond;
|
|
955
|
+
}
|
|
956
|
+
capacity;
|
|
957
|
+
refillRatePerSecond;
|
|
958
|
+
buckets = /* @__PURE__ */ new Map();
|
|
959
|
+
async consume(id, tokens = 1) {
|
|
960
|
+
const now = Date.now();
|
|
961
|
+
const bucket = this.buckets.get(id);
|
|
962
|
+
if (bucket) {
|
|
963
|
+
const delta = (now - bucket.lastRefill) / 1e3;
|
|
964
|
+
const refill = delta * this.refillRatePerSecond;
|
|
965
|
+
bucket.tokens = Math.min(this.capacity, bucket.tokens + refill);
|
|
966
|
+
bucket.lastRefill = now;
|
|
967
|
+
} else {
|
|
968
|
+
const newBucket = { tokens: this.capacity, lastRefill: now };
|
|
969
|
+
if (newBucket.tokens >= tokens) {
|
|
970
|
+
newBucket.tokens -= tokens;
|
|
971
|
+
this.buckets.set(id, newBucket);
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
this.buckets.set(id, newBucket);
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
if (bucket.tokens >= tokens) {
|
|
978
|
+
bucket.tokens -= tokens;
|
|
979
|
+
this.buckets.set(id, bucket);
|
|
980
|
+
return true;
|
|
981
|
+
}
|
|
982
|
+
this.buckets.set(id, bucket);
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
var AdaptiveRateLimiter = class {
|
|
987
|
+
constructor(config, store, securityStore, burstStore) {
|
|
988
|
+
this.config = config;
|
|
989
|
+
this.store = store || new MemoryStore(config.windowMs);
|
|
990
|
+
this.securityStore = securityStore || new MemoryStore(config.windowMs);
|
|
991
|
+
this.burstStore = burstStore || new MemoryStore(config.windowMs * 10);
|
|
992
|
+
}
|
|
993
|
+
config;
|
|
994
|
+
store;
|
|
995
|
+
securityStore;
|
|
996
|
+
burstStore;
|
|
997
|
+
ipCheckers = [];
|
|
998
|
+
/**
|
|
999
|
+
* Automates Incident Response by globally "killing" a session or IP.
|
|
1000
|
+
* This sets an infinite (effectively 1-year) block on the ID.
|
|
1001
|
+
*
|
|
1002
|
+
* @param id The client ID to block.
|
|
1003
|
+
*
|
|
1004
|
+
* @remarks
|
|
1005
|
+
* **Persistence warning**: If the underlying store is `MemoryStore` (the default),
|
|
1006
|
+
* the block is held in process memory and will be **lost on process restart**.
|
|
1007
|
+
* For a persistent kill-switch that survives restarts and works across multiple
|
|
1008
|
+
* instances, pass a `RedisStore` as the `store` and `securityStore` arguments
|
|
1009
|
+
* to the `AdaptiveRateLimiter` constructor.
|
|
1010
|
+
*
|
|
1011
|
+
* @see {@link RedisStore} for a production-grade persistent store.
|
|
1012
|
+
*/
|
|
1013
|
+
async triggerGlobalKillSwitch(id) {
|
|
1014
|
+
getTelemetryProvider().onKillSwitchTriggered?.(id);
|
|
1015
|
+
const ONE_YEAR_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
1016
|
+
await this.securityStore.setTTL(`${id}:score`, 99999, ONE_YEAR_MS);
|
|
1017
|
+
await this.store.setTTL(id, 99999, ONE_YEAR_MS);
|
|
1018
|
+
console.warn(`Mitigator: Global Kill-Switch triggered for actor: ${id}`);
|
|
1019
|
+
}
|
|
1020
|
+
registerIPChecker(checker) {
|
|
1021
|
+
this.ipCheckers.push(checker);
|
|
1022
|
+
}
|
|
1023
|
+
async recordSecurityEvent(id, weight = 1) {
|
|
1024
|
+
getTelemetryProvider().onSecurityEvent?.(id, weight);
|
|
1025
|
+
await this.securityStore.increment(`${id}:score`, weight);
|
|
1026
|
+
}
|
|
1027
|
+
async isLimited(id) {
|
|
1028
|
+
for (const checker of this.ipCheckers) {
|
|
1029
|
+
if (await checker(id)) return true;
|
|
1030
|
+
}
|
|
1031
|
+
const score = await this.securityStore.get(`${id}:score`) || 0;
|
|
1032
|
+
const bursts = await this.burstStore.get(`${id}:bursts`) || 0;
|
|
1033
|
+
const isCompromised = score >= this.config.securityThreshold || bursts >= this.config.burstThreshold;
|
|
1034
|
+
const currentLimit = isCompromised ? this.config.penaltyLimit : this.config.standardLimit;
|
|
1035
|
+
const count = await this.store.increment(id);
|
|
1036
|
+
if (count > currentLimit) {
|
|
1037
|
+
if (count === currentLimit + 1) {
|
|
1038
|
+
await this.burstStore.increment(`${id}:bursts`, 1);
|
|
1039
|
+
}
|
|
1040
|
+
getTelemetryProvider().onRateLimitBlocked?.(id, isCompromised ? "penalty" : "standard");
|
|
1041
|
+
return true;
|
|
1042
|
+
}
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
async isHighRisk(id) {
|
|
1046
|
+
const score = await this.securityStore.get(`${id}:score`) || 0;
|
|
1047
|
+
const bursts = await this.burstStore.get(`${id}:bursts`) || 0;
|
|
1048
|
+
return score >= this.config.securityThreshold || bursts >= this.config.burstThreshold;
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
var RateLimiter = class {
|
|
1052
|
+
constructor(limit, windowMs, store) {
|
|
1053
|
+
this.limit = limit;
|
|
1054
|
+
this.windowMs = windowMs;
|
|
1055
|
+
this.store = store || new MemoryStore(windowMs);
|
|
1056
|
+
}
|
|
1057
|
+
limit;
|
|
1058
|
+
windowMs;
|
|
1059
|
+
store;
|
|
1060
|
+
async isLimited(id) {
|
|
1061
|
+
const count = await this.store.increment(id);
|
|
1062
|
+
return count > this.limit;
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
// src/safe-json/index.ts
|
|
1067
|
+
var safe_json_exports = {};
|
|
1068
|
+
__export(safe_json_exports, {
|
|
1069
|
+
containsPollution: () => containsPollution,
|
|
1070
|
+
getDepth: () => getDepth,
|
|
1071
|
+
parse: () => parse
|
|
1072
|
+
});
|
|
1073
|
+
var parse = (text, maxDepth = 10) => {
|
|
1074
|
+
const obj = JSON.parse(text, (key, value) => {
|
|
1075
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
1076
|
+
return void 0;
|
|
1077
|
+
}
|
|
1078
|
+
return value;
|
|
1079
|
+
});
|
|
1080
|
+
if (getDepth(obj) > maxDepth) {
|
|
1081
|
+
throw new Error("Security Error: JSON depth limit exceeded (DoS protection).");
|
|
1082
|
+
}
|
|
1083
|
+
return obj;
|
|
1084
|
+
};
|
|
1085
|
+
var getDepth = (obj) => {
|
|
1086
|
+
if (obj === null || typeof obj !== "object") return 0;
|
|
1087
|
+
let max = 0;
|
|
1088
|
+
for (const key in obj) {
|
|
1089
|
+
if (Object.hasOwn(obj, key)) {
|
|
1090
|
+
max = Math.max(max, getDepth(obj[key]));
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return 1 + max;
|
|
1094
|
+
};
|
|
1095
|
+
var containsPollution = (obj) => {
|
|
1096
|
+
if (obj === null || typeof obj !== "object") return false;
|
|
1097
|
+
for (const key in obj) {
|
|
1098
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
1099
|
+
return true;
|
|
1100
|
+
}
|
|
1101
|
+
if (containsPollution(obj[key])) {
|
|
1102
|
+
return true;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return false;
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
// src/safe-merge/index.ts
|
|
1109
|
+
var safe_merge_exports = {};
|
|
1110
|
+
__export(safe_merge_exports, {
|
|
1111
|
+
deepFreeze: () => deepFreeze,
|
|
1112
|
+
merge: () => merge,
|
|
1113
|
+
sanitizeObject: () => sanitizeObject
|
|
1114
|
+
});
|
|
1115
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1116
|
+
var isObject = (val) => {
|
|
1117
|
+
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
1118
|
+
};
|
|
1119
|
+
var merge = (target, source, seen = /* @__PURE__ */ new WeakSet()) => {
|
|
1120
|
+
if (seen.has(source)) {
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
"Security Error: Circular reference detected during deep merge (DoS prevention)."
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
const output = { ...target };
|
|
1126
|
+
if (isObject(target) && isObject(source)) {
|
|
1127
|
+
seen.add(source);
|
|
1128
|
+
Object.keys(source).forEach((key) => {
|
|
1129
|
+
if (DANGEROUS_KEYS.has(key)) {
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const sourceValue = source[key];
|
|
1133
|
+
const targetValue = target[key];
|
|
1134
|
+
if (isObject(sourceValue) && isObject(targetValue)) {
|
|
1135
|
+
output[key] = merge(targetValue, sourceValue, seen);
|
|
1136
|
+
} else {
|
|
1137
|
+
output[key] = sourceValue;
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
return output;
|
|
1142
|
+
};
|
|
1143
|
+
var sanitizeObject = (obj, seen = /* @__PURE__ */ new WeakSet()) => {
|
|
1144
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
1145
|
+
if (seen.has(obj)) {
|
|
1146
|
+
throw new Error("Security Error: Circular reference detected during object sanitization.");
|
|
1147
|
+
}
|
|
1148
|
+
if (Array.isArray(obj)) {
|
|
1149
|
+
seen.add(obj);
|
|
1150
|
+
return obj.map((item) => sanitizeObject(item, seen));
|
|
1151
|
+
}
|
|
1152
|
+
seen.add(obj);
|
|
1153
|
+
const result = {};
|
|
1154
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1155
|
+
if (DANGEROUS_KEYS.has(key)) continue;
|
|
1156
|
+
result[key] = sanitizeObject(value, seen);
|
|
1157
|
+
}
|
|
1158
|
+
return result;
|
|
1159
|
+
};
|
|
1160
|
+
var deepFreeze = (obj) => {
|
|
1161
|
+
Object.getOwnPropertyNames(obj).forEach((name) => {
|
|
1162
|
+
const prop = obj[name];
|
|
1163
|
+
if (prop !== null && typeof prop === "object") {
|
|
1164
|
+
deepFreeze(prop);
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
return Object.freeze(obj);
|
|
1168
|
+
};
|
|
1169
|
+
|
|
1170
|
+
// src/utils/index.ts
|
|
1171
|
+
var utils_exports = {};
|
|
1172
|
+
__export(utils_exports, {
|
|
1173
|
+
SENSITIVE_KEYS: () => SENSITIVE_KEYS,
|
|
1174
|
+
SecureError: () => SecureError,
|
|
1175
|
+
auditConfig: () => auditConfig,
|
|
1176
|
+
checkSecureEnv: () => checkSecureEnv,
|
|
1177
|
+
createMTLSOptions: () => createMTLSOptions,
|
|
1178
|
+
deterministicDigitTransform: () => deterministicDigitTransform,
|
|
1179
|
+
enforceSafeQuery: () => enforceSafeQuery,
|
|
1180
|
+
lockdownPrototypes: () => lockdownPrototypes,
|
|
1181
|
+
normalizeError: () => normalizeError,
|
|
1182
|
+
redact: () => redact,
|
|
1183
|
+
standardSecurityPreset: () => standardSecurityPreset,
|
|
1184
|
+
startSelfHealingMonitor: () => startSelfHealingMonitor,
|
|
1185
|
+
wipeBuffer: () => wipeBuffer
|
|
1186
|
+
});
|
|
1187
|
+
import { createHash as createHash4 } from "crypto";
|
|
1188
|
+
var SENSITIVE_KEYS = /* @__PURE__ */ new Set([
|
|
1189
|
+
"password",
|
|
1190
|
+
"token",
|
|
1191
|
+
"secret",
|
|
1192
|
+
"key",
|
|
1193
|
+
"authorization",
|
|
1194
|
+
"apiKey",
|
|
1195
|
+
"access_token",
|
|
1196
|
+
"refresh_token",
|
|
1197
|
+
"cvv",
|
|
1198
|
+
"card_number",
|
|
1199
|
+
"ssn"
|
|
1200
|
+
]);
|
|
1201
|
+
var redact = (obj, redactKeys = SENSITIVE_KEYS) => {
|
|
1202
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
1203
|
+
if (Array.isArray(obj)) return obj.map((item) => redact(item, redactKeys));
|
|
1204
|
+
const result = {};
|
|
1205
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1206
|
+
if (redactKeys.has(key.toLowerCase())) {
|
|
1207
|
+
result[key] = "[REDACTED]";
|
|
1208
|
+
} else if (typeof value === "object") {
|
|
1209
|
+
result[key] = redact(value, redactKeys);
|
|
1210
|
+
} else {
|
|
1211
|
+
result[key] = value;
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
return result;
|
|
1215
|
+
};
|
|
1216
|
+
var SecureError = class extends Error {
|
|
1217
|
+
code;
|
|
1218
|
+
userMessage;
|
|
1219
|
+
constructor(message, code = "INTERNAL_ERROR", userMessage = "An unexpected error occurred.") {
|
|
1220
|
+
super(message);
|
|
1221
|
+
this.name = "SecureError";
|
|
1222
|
+
this.code = code;
|
|
1223
|
+
this.userMessage = userMessage;
|
|
1224
|
+
if (process.env.NODE_ENV === "production") this.stack = "";
|
|
1225
|
+
}
|
|
1226
|
+
toJSON() {
|
|
1227
|
+
return { error: true, code: this.code, message: this.userMessage };
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
var normalizeError = (error) => {
|
|
1231
|
+
if (error instanceof SecureError) return error;
|
|
1232
|
+
return new SecureError(error?.message || "Unknown error");
|
|
1233
|
+
};
|
|
1234
|
+
var wipeBuffer = (buf) => {
|
|
1235
|
+
if (Array.isArray(buf)) {
|
|
1236
|
+
for (let i = 0; i < buf.length; i++) buf[i] = 0;
|
|
1237
|
+
} else if (buf instanceof Buffer || buf instanceof Uint8Array) {
|
|
1238
|
+
buf.fill(0);
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
var startSelfHealingMonitor = (config, type, intervalMs = 6e5, logger = console) => {
|
|
1242
|
+
setInterval(() => {
|
|
1243
|
+
const issues = auditConfig(config, type);
|
|
1244
|
+
if (issues.length > 0) {
|
|
1245
|
+
logger.error("MITIGATOR CRITICAL: Self-healing monitor detected insecure config drift!");
|
|
1246
|
+
issues.forEach((issue) => logger.error(`- ${issue}`));
|
|
1247
|
+
}
|
|
1248
|
+
}, intervalMs).unref();
|
|
1249
|
+
};
|
|
1250
|
+
var enforceSafeQuery = (query, params) => {
|
|
1251
|
+
if ((!params || params.length === 0) && query.toLowerCase().includes("where")) {
|
|
1252
|
+
const suspicious = ["'", '"', "=", "--", ";"];
|
|
1253
|
+
if (suspicious.some((char) => query.includes(char))) {
|
|
1254
|
+
throw new SecureError(
|
|
1255
|
+
"Security Violation: Unparameterized query detected.",
|
|
1256
|
+
"SQL_INJECTION_RISK"
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1261
|
+
var deterministicDigitTransform = (input, secret) => {
|
|
1262
|
+
const hash = createHash4("sha256").update(input).update(secret).digest("hex");
|
|
1263
|
+
let result = "";
|
|
1264
|
+
for (let i = 0; i < input.length; i++) {
|
|
1265
|
+
const digit = Number.parseInt(input[i]);
|
|
1266
|
+
const offset = Number.parseInt(hash[i % hash.length], 16);
|
|
1267
|
+
result += ((digit + offset) % 10).toString();
|
|
1268
|
+
}
|
|
1269
|
+
return result;
|
|
1270
|
+
};
|
|
1271
|
+
var checkSecureEnv = () => {
|
|
1272
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
1273
|
+
if (isProd) {
|
|
1274
|
+
if (!process.env.SESSION_SECRET || process.env.SESSION_SECRET.length < 32) {
|
|
1275
|
+
console.warn("Security Warning: SESSION_SECRET is missing or too short.");
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
var auditConfig = (config, type) => {
|
|
1280
|
+
const issues = [];
|
|
1281
|
+
if (type === "db") {
|
|
1282
|
+
if (config.ssl === false) issues.push("Database SSL is disabled.");
|
|
1283
|
+
if (config.port === 3306 || config.port === 5432)
|
|
1284
|
+
issues.push("Database is using a default port.");
|
|
1285
|
+
} else if (type === "auth") {
|
|
1286
|
+
if (config.allowInsecurePasswordReset) issues.push("Allowing insecure password reset.");
|
|
1287
|
+
}
|
|
1288
|
+
return issues;
|
|
1289
|
+
};
|
|
1290
|
+
var lockdownPrototypes = () => {
|
|
1291
|
+
try {
|
|
1292
|
+
Object.freeze(Object.prototype);
|
|
1293
|
+
Object.freeze(Array.prototype);
|
|
1294
|
+
Object.freeze(String.prototype);
|
|
1295
|
+
} catch {
|
|
1296
|
+
console.error("Mitigator Error: Failed to lockdown prototypes.");
|
|
1297
|
+
}
|
|
1298
|
+
};
|
|
1299
|
+
var createMTLSOptions = (serverCert, serverKey, caCert) => {
|
|
1300
|
+
return {
|
|
1301
|
+
cert: serverCert,
|
|
1302
|
+
key: serverKey,
|
|
1303
|
+
ca: caCert,
|
|
1304
|
+
requestCert: true,
|
|
1305
|
+
rejectUnauthorized: true
|
|
1306
|
+
};
|
|
1307
|
+
};
|
|
1308
|
+
var standardSecurityPreset = {
|
|
1309
|
+
trustProxy: true,
|
|
1310
|
+
strictTransportSecurity: { maxAge: 31536e3, includeSubDomains: true, preload: true },
|
|
1311
|
+
contentSecurityPolicy: true,
|
|
1312
|
+
referrerPolicy: "strict-origin-when-cross-origin"
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
// src/utils/presets.ts
|
|
1316
|
+
var presets_exports = {};
|
|
1317
|
+
__export(presets_exports, {
|
|
1318
|
+
NestJsMitigatorMiddleware: () => NestJsMitigatorMiddleware,
|
|
1319
|
+
SecureLoggerChain: () => SecureLoggerChain,
|
|
1320
|
+
createSecureLogger: () => createSecureLogger,
|
|
1321
|
+
expressErrorHandler: () => expressErrorHandler,
|
|
1322
|
+
expressMiddleware: () => expressMiddleware,
|
|
1323
|
+
fastifyPlugin: () => fastifyPlugin,
|
|
1324
|
+
nextJsMiddleware: () => nextJsMiddleware
|
|
1325
|
+
});
|
|
1326
|
+
import { createHash as createHash5 } from "crypto";
|
|
1327
|
+
var expressMiddleware = (options = {}) => {
|
|
1328
|
+
const limiter = options.rateLimit ? new RateLimiter(
|
|
1329
|
+
options.rateLimitMax || 100,
|
|
1330
|
+
options.rateLimitWindowMs || 6e4,
|
|
1331
|
+
new MemoryStore(options.rateLimitWindowMs || 6e4)
|
|
1332
|
+
) : null;
|
|
1333
|
+
return async (req, res, next) => {
|
|
1334
|
+
Object.entries(standardHeaders).forEach(([key, value]) => {
|
|
1335
|
+
res.setHeader(key, value);
|
|
1336
|
+
});
|
|
1337
|
+
if (limiter) {
|
|
1338
|
+
const ip = req.ip || req.connection.remoteAddress || "unknown";
|
|
1339
|
+
if (await limiter.isLimited(ip)) {
|
|
1340
|
+
return res.status(429).json({ error: "Too Many Requests" });
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
try {
|
|
1344
|
+
if (req.body && typeof req.body === "object") {
|
|
1345
|
+
if (scanForSecrets(req.body)) {
|
|
1346
|
+
return next(
|
|
1347
|
+
new SecureError("Security Violation: Potential secret leakage detected.", "BAD_INPUT")
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
} catch {
|
|
1352
|
+
return next(new SecureError("Security Violation: Suspicious input detected.", "BAD_INPUT"));
|
|
1353
|
+
}
|
|
1354
|
+
req.mitigator = {
|
|
1355
|
+
sanitize: (html) => sanitizeHtml(html),
|
|
1356
|
+
safeJson: (text) => parse(text)
|
|
1357
|
+
};
|
|
1358
|
+
next();
|
|
1359
|
+
};
|
|
1360
|
+
};
|
|
1361
|
+
var nextJsMiddleware = (_req, res) => {
|
|
1362
|
+
Object.entries(standardHeaders).forEach(([key, value]) => {
|
|
1363
|
+
res.headers.set(key, value);
|
|
1364
|
+
});
|
|
1365
|
+
return res;
|
|
1366
|
+
};
|
|
1367
|
+
var expressErrorHandler = (err, _req, res, _next) => {
|
|
1368
|
+
const secureErr = err instanceof SecureError ? err : new SecureError(err.message);
|
|
1369
|
+
res.status(secureErr.code === "BAD_INPUT" ? 400 : 500).json(secureErr.toJSON());
|
|
1370
|
+
};
|
|
1371
|
+
var SecureLoggerChain = class {
|
|
1372
|
+
constructor(baseLogger) {
|
|
1373
|
+
this.baseLogger = baseLogger;
|
|
1374
|
+
this.lastHash = createHash5("sha256").update(Date.now().toString()).digest("hex");
|
|
1375
|
+
}
|
|
1376
|
+
baseLogger;
|
|
1377
|
+
lastHash = "";
|
|
1378
|
+
chain(msg, data) {
|
|
1379
|
+
const payload = JSON.stringify({ msg, data, prev: this.lastHash });
|
|
1380
|
+
this.lastHash = createHash5("sha256").update(payload).digest("hex");
|
|
1381
|
+
return this.lastHash;
|
|
1382
|
+
}
|
|
1383
|
+
info(msg, data) {
|
|
1384
|
+
const hash = this.chain(msg, data);
|
|
1385
|
+
this.baseLogger.info(`[CHAIN:${hash}] ${msg}`, redact(data));
|
|
1386
|
+
}
|
|
1387
|
+
warn(msg, data) {
|
|
1388
|
+
if (scanForSecrets(data)) {
|
|
1389
|
+
this.baseLogger.warn("CRITICAL: Secret leakage blocked!");
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const hash = this.chain(msg, data);
|
|
1393
|
+
this.baseLogger.warn(`[CHAIN:${hash}] ${msg}`, redact(data));
|
|
1394
|
+
}
|
|
1395
|
+
error(msg, err) {
|
|
1396
|
+
const secureErr = err instanceof SecureError ? err : new SecureError(err?.message || "Error");
|
|
1397
|
+
const hash = this.chain(msg, secureErr.toJSON());
|
|
1398
|
+
this.baseLogger.error(`[CHAIN:${hash}] ${msg}`, redact(secureErr.toJSON()));
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
var createSecureLogger = (baseLogger) => new SecureLoggerChain(baseLogger);
|
|
1402
|
+
var fastifyPlugin = (options = {}) => {
|
|
1403
|
+
const limiter = options.rateLimit ? new RateLimiter(
|
|
1404
|
+
options.rateLimitMax || 100,
|
|
1405
|
+
options.rateLimitWindowMs || 6e4,
|
|
1406
|
+
new MemoryStore(options.rateLimitWindowMs || 6e4)
|
|
1407
|
+
) : null;
|
|
1408
|
+
return async (fastify) => {
|
|
1409
|
+
fastify.addHook("onRequest", async (req, reply) => {
|
|
1410
|
+
Object.entries(standardHeaders).forEach(([key, value]) => {
|
|
1411
|
+
reply.header(key, value);
|
|
1412
|
+
});
|
|
1413
|
+
if (limiter) {
|
|
1414
|
+
const ip = req.ip || "unknown";
|
|
1415
|
+
if (await limiter.isLimited(ip)) {
|
|
1416
|
+
reply.code(429).send({ error: "Too Many Requests" });
|
|
1417
|
+
return reply;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
fastify.addHook("preHandler", async (req, reply) => {
|
|
1422
|
+
try {
|
|
1423
|
+
if (req.body && typeof req.body === "object") {
|
|
1424
|
+
if (scanForSecrets(req.body)) {
|
|
1425
|
+
throw new SecureError(
|
|
1426
|
+
"Security Violation: Potential secret leakage detected.",
|
|
1427
|
+
"BAD_INPUT"
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
} catch (err) {
|
|
1432
|
+
const secureErr = err instanceof SecureError ? err : new SecureError(err.message, "BAD_INPUT");
|
|
1433
|
+
reply.code(400).send(secureErr.toJSON());
|
|
1434
|
+
return reply;
|
|
1435
|
+
}
|
|
1436
|
+
req.mitigator = {
|
|
1437
|
+
sanitize: (html) => sanitizeHtml(html),
|
|
1438
|
+
safeJson: (text) => parse(text)
|
|
1439
|
+
};
|
|
1440
|
+
});
|
|
1441
|
+
};
|
|
1442
|
+
};
|
|
1443
|
+
var NestJsMitigatorMiddleware = class _NestJsMitigatorMiddleware {
|
|
1444
|
+
static limiter = null;
|
|
1445
|
+
static configure(options = {}) {
|
|
1446
|
+
if (options.rateLimit) {
|
|
1447
|
+
this.limiter = new RateLimiter(
|
|
1448
|
+
options.rateLimitMax || 100,
|
|
1449
|
+
options.rateLimitWindowMs || 6e4,
|
|
1450
|
+
new MemoryStore(options.rateLimitWindowMs || 6e4)
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
async use(req, res, next) {
|
|
1455
|
+
Object.entries(standardHeaders).forEach(([key, value]) => {
|
|
1456
|
+
res.setHeader(key, value);
|
|
1457
|
+
});
|
|
1458
|
+
if (_NestJsMitigatorMiddleware.limiter) {
|
|
1459
|
+
const ip = req.ip || req.connection?.remoteAddress || "unknown";
|
|
1460
|
+
if (await _NestJsMitigatorMiddleware.limiter.isLimited(ip)) {
|
|
1461
|
+
res.status(429).json({ error: "Too Many Requests" });
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
try {
|
|
1466
|
+
if (req.body && typeof req.body === "object") {
|
|
1467
|
+
if (scanForSecrets(req.body)) {
|
|
1468
|
+
throw new SecureError(
|
|
1469
|
+
"Security Violation: Potential secret leakage detected.",
|
|
1470
|
+
"BAD_INPUT"
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
const secureErr = err instanceof SecureError ? err : new SecureError(err.message, "BAD_INPUT");
|
|
1476
|
+
res.status(400).json(secureErr.toJSON());
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
req.mitigator = {
|
|
1480
|
+
sanitize: (html) => sanitizeHtml(html),
|
|
1481
|
+
safeJson: (text) => parse(text)
|
|
1482
|
+
};
|
|
1483
|
+
next();
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
export {
|
|
1487
|
+
auth_exports as auth,
|
|
1488
|
+
crypto_exports as crypto,
|
|
1489
|
+
fs_exports as fs,
|
|
1490
|
+
headers_exports as headers,
|
|
1491
|
+
http_exports as http,
|
|
1492
|
+
presets_exports as presets,
|
|
1493
|
+
rate_limit_exports as rateLimit,
|
|
1494
|
+
safe_json_exports as safeJson,
|
|
1495
|
+
safe_merge_exports as safeMerge,
|
|
1496
|
+
sanitize_exports as sanitize,
|
|
1497
|
+
telemetry_exports as telemetry,
|
|
1498
|
+
utils_exports as utils,
|
|
1499
|
+
validate_exports as validate
|
|
1500
|
+
};
|
|
1501
|
+
//# sourceMappingURL=index.js.map
|