@arcis/node 1.3.0 → 1.4.2
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 +1 -1
- package/dist/core/{index.d.mts → constants.d.ts} +21 -70
- package/dist/core/constants.d.ts.map +1 -0
- package/dist/core/errors.d.ts +53 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/index.d.ts +6 -168
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +11 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +11 -3
- package/dist/core/index.mjs.map +1 -1
- package/dist/{types-BOkx5YJc.d.mts → core/types.d.ts} +27 -30
- package/dist/core/types.d.ts.map +1 -0
- package/dist/index.d.ts +71 -166
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +182 -48
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +182 -50
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.d.ts +4 -36
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs.map +1 -1
- package/dist/logging/{index.d.mts → redactor.d.ts} +5 -9
- package/dist/logging/redactor.d.ts.map +1 -0
- package/dist/middleware/bot-detection.d.ts +86 -0
- package/dist/middleware/bot-detection.d.ts.map +1 -0
- package/dist/middleware/cookies.d.ts +48 -0
- package/dist/middleware/cookies.d.ts.map +1 -0
- package/dist/middleware/cors.d.ts +65 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/csrf.d.ts +109 -0
- package/dist/middleware/csrf.d.ts.map +1 -0
- package/dist/middleware/error-handler.d.ts +43 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/headers.d.ts +29 -0
- package/dist/middleware/headers.d.ts.map +1 -0
- package/dist/middleware/hpp.d.ts +56 -0
- package/dist/middleware/hpp.d.ts.map +1 -0
- package/dist/middleware/index.d.ts +16 -3
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +68 -31
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +69 -32
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/middleware/main.d.ts +40 -0
- package/dist/middleware/main.d.ts.map +1 -0
- package/dist/middleware/rate-limit-sliding.d.ts +46 -0
- package/dist/middleware/rate-limit-sliding.d.ts.map +1 -0
- package/dist/middleware/rate-limit-token.d.ts +51 -0
- package/dist/middleware/rate-limit-token.d.ts.map +1 -0
- package/dist/middleware/rate-limit.d.ts +34 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/sanitizers/command.d.ts +28 -0
- package/dist/sanitizers/command.d.ts.map +1 -0
- package/dist/sanitizers/encode.d.ts +46 -0
- package/dist/sanitizers/encode.d.ts.map +1 -0
- package/dist/sanitizers/headers.d.ts +46 -0
- package/dist/sanitizers/headers.d.ts.map +1 -0
- package/dist/sanitizers/index.d.ts +18 -22
- package/dist/sanitizers/index.d.ts.map +1 -0
- package/dist/sanitizers/index.js +90 -32
- package/dist/sanitizers/index.js.map +1 -1
- package/dist/sanitizers/index.mjs +88 -33
- package/dist/sanitizers/index.mjs.map +1 -1
- package/dist/sanitizers/jsonp.d.ts +34 -0
- package/dist/sanitizers/jsonp.d.ts.map +1 -0
- package/dist/sanitizers/ldap.d.ts +42 -0
- package/dist/sanitizers/ldap.d.ts.map +1 -0
- package/dist/sanitizers/nosql.d.ts +31 -0
- package/dist/sanitizers/nosql.d.ts.map +1 -0
- package/dist/sanitizers/path.d.ts +28 -0
- package/dist/sanitizers/path.d.ts.map +1 -0
- package/dist/sanitizers/pii.d.ts +80 -0
- package/dist/sanitizers/pii.d.ts.map +1 -0
- package/dist/sanitizers/prototype.d.ts +34 -0
- package/dist/sanitizers/prototype.d.ts.map +1 -0
- package/dist/sanitizers/sanitize.d.ts +51 -0
- package/dist/sanitizers/sanitize.d.ts.map +1 -0
- package/dist/sanitizers/sql.d.ts +28 -0
- package/dist/sanitizers/sql.d.ts.map +1 -0
- package/dist/sanitizers/ssti.d.ts +20 -0
- package/dist/sanitizers/ssti.d.ts.map +1 -0
- package/dist/sanitizers/utils.d.ts +19 -0
- package/dist/sanitizers/utils.d.ts.map +1 -0
- package/dist/sanitizers/xss.d.ts +35 -0
- package/dist/sanitizers/xss.d.ts.map +1 -0
- package/dist/sanitizers/xxe.d.ts +20 -0
- package/dist/sanitizers/xxe.d.ts.map +1 -0
- package/dist/stores/index.d.ts +6 -104
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +21 -1
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/index.mjs +21 -1
- package/dist/stores/index.mjs.map +1 -1
- package/dist/stores/memory.d.ts +29 -0
- package/dist/stores/memory.d.ts.map +1 -0
- package/dist/stores/{index.d.mts → redis.d.ts} +6 -45
- package/dist/stores/redis.d.ts.map +1 -0
- package/dist/utils/duration.d.ts +34 -0
- package/dist/utils/duration.d.ts.map +1 -0
- package/dist/utils/fingerprint.d.ts +64 -0
- package/dist/utils/fingerprint.d.ts.map +1 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +188 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +182 -0
- package/dist/utils/index.mjs.map +1 -0
- package/dist/utils/ip.d.ts +70 -0
- package/dist/utils/ip.d.ts.map +1 -0
- package/dist/validation/email.d.ts +82 -0
- package/dist/validation/email.d.ts.map +1 -0
- package/dist/validation/file.d.ts +90 -0
- package/dist/validation/file.d.ts.map +1 -0
- package/dist/validation/index.d.ts +10 -3
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +38 -21
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/index.mjs +38 -21
- package/dist/validation/index.mjs.map +1 -1
- package/dist/validation/redirect.d.ts +64 -0
- package/dist/validation/redirect.d.ts.map +1 -0
- package/dist/validation/schema.d.ts +36 -0
- package/dist/validation/schema.d.ts.map +1 -0
- package/dist/validation/url.d.ts +65 -0
- package/dist/validation/url.d.ts.map +1 -0
- package/package.json +8 -6
- package/dist/encode-CrQCGlBq.d.mts +0 -484
- package/dist/encode-jl9sOwmA.d.ts +0 -484
- package/dist/index-BAhgn9V2.d.ts +0 -532
- package/dist/index-BGNKspqH.d.ts +0 -340
- package/dist/index-Cd02z-0j.d.mts +0 -340
- package/dist/index-DgJtWMSj.d.mts +0 -532
- package/dist/index.d.mts +0 -175
- package/dist/middleware/index.d.mts +0 -3
- package/dist/sanitizers/index.d.mts +0 -24
- package/dist/types-BOkx5YJc.d.ts +0 -279
- package/dist/validation/index.d.mts +0 -3
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { promises } from 'dns';
|
|
2
|
-
import { randomBytes, createHash } from 'crypto';
|
|
2
|
+
import { randomBytes, timingSafeEqual, createHash } from 'crypto';
|
|
3
3
|
|
|
4
4
|
// src/core/constants.ts
|
|
5
5
|
var INPUT = {
|
|
@@ -66,7 +66,15 @@ var XSS_PATTERNS = [
|
|
|
66
66
|
/** URL-encoded script tags */
|
|
67
67
|
/%3Cscript/gi,
|
|
68
68
|
/** SVG with onload */
|
|
69
|
-
/<svg[^>]*onload/gi
|
|
69
|
+
/<svg[^>]*onload/gi,
|
|
70
|
+
/** form tags — phishing/credential harvesting via action= redirection */
|
|
71
|
+
/<form[\s>]/gi,
|
|
72
|
+
/** meta tags — http-equiv refresh redirects or CSP bypass */
|
|
73
|
+
/<meta[\s>]/gi,
|
|
74
|
+
/** base href hijacking — redirects all relative URLs to attacker domain */
|
|
75
|
+
/<base[\s>]/gi,
|
|
76
|
+
/** link tag injection — stylesheet or preload CSRF attacks */
|
|
77
|
+
/<link[\s>]/gi
|
|
70
78
|
];
|
|
71
79
|
var XSS_REMOVE_PATTERNS = [
|
|
72
80
|
/** Full script blocks (content + tags) */
|
|
@@ -93,7 +101,15 @@ var XSS_REMOVE_PATTERNS = [
|
|
|
93
101
|
/javascript\s*:/gi,
|
|
94
102
|
/vbscript\s*:/gi,
|
|
95
103
|
/** data: URIs with HTML/script content */
|
|
96
|
-
/data\s*:\s*text\/html[^>\s]*/gi
|
|
104
|
+
/data\s*:\s*text\/html[^>\s]*/gi,
|
|
105
|
+
/** form tag injection — phishing via action= redirection */
|
|
106
|
+
/<form[\s>][^>]*/gi,
|
|
107
|
+
/** meta tag injection — http-equiv refresh or CSP bypass */
|
|
108
|
+
/<meta[\s>][^>]*/gi,
|
|
109
|
+
/** base href hijacking */
|
|
110
|
+
/<base[\s>][^>]*/gi,
|
|
111
|
+
/** link tag injection — stylesheet or preload attacks */
|
|
112
|
+
/<link[\s>][^>]*/gi
|
|
97
113
|
];
|
|
98
114
|
var SQL_PATTERNS = [
|
|
99
115
|
/** SQL keywords */
|
|
@@ -157,8 +173,8 @@ var COMMAND_PATTERNS = [
|
|
|
157
173
|
/[;&|`]/g,
|
|
158
174
|
/** Command substitution: $( ... ) — matched as a pair to reduce false positives */
|
|
159
175
|
/\$\(/g,
|
|
160
|
-
/** URL-encoded
|
|
161
|
-
/%0[
|
|
176
|
+
/** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
|
|
177
|
+
/%0[0-9a-f]/gi
|
|
162
178
|
];
|
|
163
179
|
var DANGEROUS_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
164
180
|
"__proto__",
|
|
@@ -449,7 +465,24 @@ function createRateLimiter(options = {}) {
|
|
|
449
465
|
}
|
|
450
466
|
next();
|
|
451
467
|
} catch (error) {
|
|
452
|
-
console.error("[arcis] Rate limiter error:", error);
|
|
468
|
+
console.error("[arcis] Rate limiter store error, using in-memory fallback:", error);
|
|
469
|
+
try {
|
|
470
|
+
const key = keyGenerator(req);
|
|
471
|
+
const now = Date.now();
|
|
472
|
+
if (!inMemoryStore[key] || inMemoryStore[key].resetTime < now) {
|
|
473
|
+
inMemoryStore[key] = { count: 1, resetTime: now + windowMs };
|
|
474
|
+
} else {
|
|
475
|
+
inMemoryStore[key].count++;
|
|
476
|
+
}
|
|
477
|
+
const count = inMemoryStore[key].count;
|
|
478
|
+
if (count > max) {
|
|
479
|
+
const resetSeconds = Math.ceil((inMemoryStore[key].resetTime - now) / 1e3);
|
|
480
|
+
res.setHeader("Retry-After", resetSeconds.toString());
|
|
481
|
+
res.status(statusCode).json({ error: message, retryAfter: resetSeconds });
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
453
486
|
next();
|
|
454
487
|
}
|
|
455
488
|
};
|
|
@@ -693,26 +726,31 @@ function sanitizePath(input, collectThreats = false) {
|
|
|
693
726
|
const threats = [];
|
|
694
727
|
let value = input;
|
|
695
728
|
let wasSanitized = false;
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
729
|
+
value = value.normalize("NFKC");
|
|
730
|
+
let prev;
|
|
731
|
+
do {
|
|
732
|
+
prev = value;
|
|
733
|
+
for (const pattern of PATH_PATTERNS) {
|
|
699
734
|
pattern.lastIndex = 0;
|
|
700
|
-
if (
|
|
701
|
-
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
735
|
+
if (pattern.test(value)) {
|
|
736
|
+
pattern.lastIndex = 0;
|
|
737
|
+
if (collectThreats) {
|
|
738
|
+
const matches = value.match(pattern);
|
|
739
|
+
if (matches) {
|
|
740
|
+
for (const match of matches) {
|
|
741
|
+
threats.push({
|
|
742
|
+
type: "path_traversal",
|
|
743
|
+
pattern: pattern.source,
|
|
744
|
+
original: match
|
|
745
|
+
});
|
|
746
|
+
}
|
|
709
747
|
}
|
|
710
748
|
}
|
|
749
|
+
value = value.replace(pattern, "");
|
|
750
|
+
wasSanitized = true;
|
|
711
751
|
}
|
|
712
|
-
value = value.replace(pattern, "");
|
|
713
|
-
wasSanitized = true;
|
|
714
752
|
}
|
|
715
|
-
}
|
|
753
|
+
} while (value !== prev);
|
|
716
754
|
if (collectThreats) {
|
|
717
755
|
return { value, wasSanitized, threats };
|
|
718
756
|
}
|
|
@@ -720,9 +758,10 @@ function sanitizePath(input, collectThreats = false) {
|
|
|
720
758
|
}
|
|
721
759
|
function detectPathTraversal(input) {
|
|
722
760
|
if (typeof input !== "string") return false;
|
|
761
|
+
const normalized = input.normalize("NFKC");
|
|
723
762
|
for (const pattern of PATH_PATTERNS) {
|
|
724
763
|
pattern.lastIndex = 0;
|
|
725
|
-
if (pattern.test(
|
|
764
|
+
if (pattern.test(normalized)) {
|
|
726
765
|
return true;
|
|
727
766
|
}
|
|
728
767
|
}
|
|
@@ -780,7 +819,7 @@ function sanitizeString(value, options = {}) {
|
|
|
780
819
|
if (value.length > maxSize) {
|
|
781
820
|
throw new InputTooLargeError(maxSize, value.length);
|
|
782
821
|
}
|
|
783
|
-
const reject = options.mode
|
|
822
|
+
const reject = options.mode === "reject";
|
|
784
823
|
let result = value;
|
|
785
824
|
if (options.sql !== false) {
|
|
786
825
|
if (reject) {
|
|
@@ -929,10 +968,22 @@ var SSTI_DETECT_PATTERNS = [
|
|
|
929
968
|
/\{\{\s*(?:self|request|lipsum|cycler|joiner|namespace|range)\b/gi
|
|
930
969
|
];
|
|
931
970
|
var SSTI_REMOVE_PATTERNS = [
|
|
971
|
+
/** Jinja2 / Twig: {{ ... }} — always strip (not valid in any JS context) */
|
|
932
972
|
/\{\{.*?\}\}/g,
|
|
933
|
-
|
|
973
|
+
/**
|
|
974
|
+
* Freemarker / Spring EL: ${...} — only strip when the expression contains
|
|
975
|
+
* operators (?!*+-/), method calls (), or known-dangerous prefixes.
|
|
976
|
+
* Bare ${name} and ${user.name} are left intact (JS template literal syntax).
|
|
977
|
+
*/
|
|
978
|
+
/\$\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
979
|
+
/** ERB / EJS: <%= ... %> */
|
|
934
980
|
/<%[=\-]?.*?%>/gs,
|
|
935
|
-
|
|
981
|
+
/**
|
|
982
|
+
* Pug / Jade: #{...} — same narrowing as ${ above.
|
|
983
|
+
* #{name} output expressions are left intact.
|
|
984
|
+
*/
|
|
985
|
+
/#\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
986
|
+
/** Python dunder sandbox escape — always strip */
|
|
936
987
|
/__(?:class|mro|subclasses|globals|builtins|import)__/gi
|
|
937
988
|
];
|
|
938
989
|
function sanitizeSsti(input, collectThreats = false) {
|
|
@@ -1296,16 +1347,18 @@ function encodeForAttribute(value) {
|
|
|
1296
1347
|
function encodeForJs(value) {
|
|
1297
1348
|
if (!value) return "";
|
|
1298
1349
|
let result = "";
|
|
1299
|
-
for (
|
|
1300
|
-
const
|
|
1301
|
-
if (
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
result +=
|
|
1305
|
-
} else if (
|
|
1306
|
-
result += `\\x${
|
|
1350
|
+
for (const char of value) {
|
|
1351
|
+
const cp = char.codePointAt(0);
|
|
1352
|
+
if (cp >= 48 && cp <= 57 || // 0-9
|
|
1353
|
+
cp >= 65 && cp <= 90 || // A-Z
|
|
1354
|
+
cp >= 97 && cp <= 122) {
|
|
1355
|
+
result += char;
|
|
1356
|
+
} else if (cp < 256) {
|
|
1357
|
+
result += `\\x${cp.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
1358
|
+
} else if (cp <= 65535) {
|
|
1359
|
+
result += `\\u${cp.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
1307
1360
|
} else {
|
|
1308
|
-
result += `\\u${
|
|
1361
|
+
result += `\\u{${cp.toString(16).toUpperCase()}}`;
|
|
1309
1362
|
}
|
|
1310
1363
|
}
|
|
1311
1364
|
return result;
|
|
@@ -1758,8 +1811,12 @@ function checkPrivateIp(hostname) {
|
|
|
1758
1811
|
if (hostname === "metadata.google.internal" || hostname === "metadata.internal" || hostname === "metadata.azure.internal") {
|
|
1759
1812
|
return "cloud metadata endpoint";
|
|
1760
1813
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1814
|
+
let ipv6 = hostname.replace(/^\[|\]$/g, "");
|
|
1815
|
+
const zoneIdx = ipv6.indexOf("%");
|
|
1816
|
+
if (zoneIdx !== -1) {
|
|
1817
|
+
ipv6 = ipv6.slice(0, zoneIdx);
|
|
1818
|
+
}
|
|
1819
|
+
if (ipv6 === "::1" || ipv6 === "::" || /^fc[0-9a-f]{2}:/i.test(ipv6) || /^fd[0-9a-f]{2}:/i.test(ipv6) || /^fe80:/i.test(ipv6) || /^ff[0-9a-f]{2}:/i.test(ipv6)) {
|
|
1763
1820
|
return "private IPv6 address";
|
|
1764
1821
|
}
|
|
1765
1822
|
const mappedDotted = ipv6.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
|
|
@@ -2818,11 +2875,9 @@ function generateCsrfToken(length = 32) {
|
|
|
2818
2875
|
function validateCsrfToken(cookieToken, requestToken) {
|
|
2819
2876
|
if (!cookieToken || !requestToken) return false;
|
|
2820
2877
|
if (cookieToken.length !== requestToken.length) return false;
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
}
|
|
2825
|
-
return result === 0;
|
|
2878
|
+
const a = Buffer.from(cookieToken);
|
|
2879
|
+
const b = Buffer.from(requestToken);
|
|
2880
|
+
return timingSafeEqual(a, b);
|
|
2826
2881
|
}
|
|
2827
2882
|
function getRequestToken(req, headerName, fieldName) {
|
|
2828
2883
|
const headerToken = req.headers[headerName.toLowerCase()];
|
|
@@ -2831,19 +2886,17 @@ function getRequestToken(req, headerName, fieldName) {
|
|
|
2831
2886
|
const bodyToken = req.body[fieldName];
|
|
2832
2887
|
if (typeof bodyToken === "string" && bodyToken) return bodyToken;
|
|
2833
2888
|
}
|
|
2834
|
-
if (req.query && fieldName in req.query) {
|
|
2835
|
-
const queryToken = req.query[fieldName];
|
|
2836
|
-
if (typeof queryToken === "string" && queryToken) return queryToken;
|
|
2837
|
-
}
|
|
2838
2889
|
return void 0;
|
|
2839
2890
|
}
|
|
2840
2891
|
function csrfProtection(options = {}) {
|
|
2841
|
-
const
|
|
2892
|
+
const baseCookieName = options.cookieName ?? DEFAULTS.cookieName;
|
|
2893
|
+
const cookieName = options.useHostPrefix ? `__Host-${baseCookieName}` : baseCookieName;
|
|
2842
2894
|
const headerName = options.headerName ?? DEFAULTS.headerName;
|
|
2843
2895
|
const fieldName = options.fieldName ?? DEFAULTS.fieldName;
|
|
2844
2896
|
const tokenLength = options.tokenLength ?? DEFAULTS.tokenLength;
|
|
2845
2897
|
const protectedMethods = options.protectedMethods ?? [...DEFAULTS.protectedMethods];
|
|
2846
2898
|
const excludePaths = options.excludePaths ?? [];
|
|
2899
|
+
const skipCsrf = options.skipCsrf;
|
|
2847
2900
|
const isProduction = process.env.NODE_ENV === "production";
|
|
2848
2901
|
const cookieOpts = {
|
|
2849
2902
|
path: options.cookie?.path ?? "/",
|
|
@@ -2863,6 +2916,9 @@ function csrfProtection(options = {}) {
|
|
|
2863
2916
|
const protectedSet = new Set(protectedMethods.map((m) => m.toUpperCase()));
|
|
2864
2917
|
return (req, res, next) => {
|
|
2865
2918
|
const method = req.method.toUpperCase();
|
|
2919
|
+
if (skipCsrf && skipCsrf(req)) {
|
|
2920
|
+
return next();
|
|
2921
|
+
}
|
|
2866
2922
|
const requestPath = req.path || req.url;
|
|
2867
2923
|
if (excludePaths.some((p) => requestPath === p || requestPath.startsWith(p + "/"))) {
|
|
2868
2924
|
return next();
|
|
@@ -2912,13 +2968,69 @@ function setCsrfCookie(res, name, token, opts) {
|
|
|
2912
2968
|
if (opts.secure) parts.push("Secure");
|
|
2913
2969
|
parts.push(`SameSite=${opts.sameSite}`);
|
|
2914
2970
|
if (opts.domain) parts.push(`Domain=${opts.domain}`);
|
|
2915
|
-
|
|
2971
|
+
const newCookie = parts.join("; ");
|
|
2972
|
+
const existing = res.getHeader("Set-Cookie");
|
|
2973
|
+
if (existing === void 0) {
|
|
2974
|
+
res.setHeader("Set-Cookie", newCookie);
|
|
2975
|
+
} else if (Array.isArray(existing)) {
|
|
2976
|
+
res.setHeader("Set-Cookie", [...existing, newCookie]);
|
|
2977
|
+
} else {
|
|
2978
|
+
res.setHeader("Set-Cookie", [existing, newCookie]);
|
|
2979
|
+
}
|
|
2916
2980
|
}
|
|
2917
2981
|
function escapeRegex(str) {
|
|
2918
2982
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2919
2983
|
}
|
|
2920
2984
|
var createCsrf = csrfProtection;
|
|
2921
2985
|
|
|
2986
|
+
// src/middleware/hpp.ts
|
|
2987
|
+
function hpp(options = {}) {
|
|
2988
|
+
const whitelist = new Set(options.whitelist ?? []);
|
|
2989
|
+
const checkQuery = options.checkQuery ?? true;
|
|
2990
|
+
const checkBody = options.checkBody ?? true;
|
|
2991
|
+
return (req, _res, next) => {
|
|
2992
|
+
if (checkQuery && req.query && typeof req.query === "object") {
|
|
2993
|
+
const polluted = {};
|
|
2994
|
+
const clean = {};
|
|
2995
|
+
for (const [key, value] of Object.entries(req.query)) {
|
|
2996
|
+
if (Array.isArray(value)) {
|
|
2997
|
+
const strings = value.filter((v) => typeof v === "string");
|
|
2998
|
+
if (whitelist.has(key)) {
|
|
2999
|
+
clean[key] = strings;
|
|
3000
|
+
} else {
|
|
3001
|
+
polluted[key] = strings;
|
|
3002
|
+
clean[key] = strings[strings.length - 1] ?? "";
|
|
3003
|
+
}
|
|
3004
|
+
} else {
|
|
3005
|
+
clean[key] = value;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
req.queryPolluted = polluted;
|
|
3009
|
+
Object.defineProperty(req, "query", { value: clean, writable: true, configurable: true });
|
|
3010
|
+
}
|
|
3011
|
+
if (checkBody && req.body && typeof req.body === "object" && !Array.isArray(req.body)) {
|
|
3012
|
+
const polluted = {};
|
|
3013
|
+
const clean = {};
|
|
3014
|
+
for (const [key, value] of Object.entries(req.body)) {
|
|
3015
|
+
if (Array.isArray(value)) {
|
|
3016
|
+
if (whitelist.has(key)) {
|
|
3017
|
+
clean[key] = value;
|
|
3018
|
+
} else {
|
|
3019
|
+
polluted[key] = value;
|
|
3020
|
+
clean[key] = value[value.length - 1];
|
|
3021
|
+
}
|
|
3022
|
+
} else {
|
|
3023
|
+
clean[key] = value;
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
req.bodyPolluted = polluted;
|
|
3027
|
+
Object.defineProperty(req, "body", { value: clean, writable: true, configurable: true });
|
|
3028
|
+
}
|
|
3029
|
+
next();
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
var createHpp = hpp;
|
|
3033
|
+
|
|
2922
3034
|
// src/utils/ip.ts
|
|
2923
3035
|
var PLATFORM_HEADERS = {
|
|
2924
3036
|
cloudflare: "cf-connecting-ip",
|
|
@@ -3039,7 +3151,7 @@ function fingerprint(req, options = {}) {
|
|
|
3039
3151
|
components.push(`enc:${getHeader2(req, "accept-encoding")}`);
|
|
3040
3152
|
}
|
|
3041
3153
|
for (const c of custom) {
|
|
3042
|
-
if (c
|
|
3154
|
+
if (c !== null && c !== void 0) components.push(`custom:${c}`);
|
|
3043
3155
|
}
|
|
3044
3156
|
components.sort();
|
|
3045
3157
|
const hash = createHash("sha256");
|
|
@@ -3048,8 +3160,9 @@ function fingerprint(req, options = {}) {
|
|
|
3048
3160
|
}
|
|
3049
3161
|
|
|
3050
3162
|
// src/stores/memory.ts
|
|
3163
|
+
var DEFAULT_MAX_SIZE2 = 1e4;
|
|
3051
3164
|
var MemoryStore = class {
|
|
3052
|
-
constructor(windowMs = RATE_LIMIT.DEFAULT_WINDOW_MS) {
|
|
3165
|
+
constructor(windowMs = RATE_LIMIT.DEFAULT_WINDOW_MS, maxSize = DEFAULT_MAX_SIZE2) {
|
|
3053
3166
|
this.store = /* @__PURE__ */ new Map();
|
|
3054
3167
|
this.cleanupInterval = null;
|
|
3055
3168
|
if (!Number.isFinite(windowMs) || windowMs < RATE_LIMIT.MIN_WINDOW_MS) {
|
|
@@ -3057,7 +3170,11 @@ var MemoryStore = class {
|
|
|
3057
3170
|
`MemoryStore: windowMs must be a finite number >= ${RATE_LIMIT.MIN_WINDOW_MS} (got ${windowMs})`
|
|
3058
3171
|
);
|
|
3059
3172
|
}
|
|
3173
|
+
if (!Number.isFinite(maxSize) || maxSize < 1) {
|
|
3174
|
+
throw new RangeError(`MemoryStore: maxSize must be >= 1 (got ${maxSize})`);
|
|
3175
|
+
}
|
|
3060
3176
|
this.windowMs = windowMs;
|
|
3177
|
+
this.maxSize = maxSize;
|
|
3061
3178
|
this.startCleanup();
|
|
3062
3179
|
}
|
|
3063
3180
|
/**
|
|
@@ -3089,18 +3206,33 @@ var MemoryStore = class {
|
|
|
3089
3206
|
return entry;
|
|
3090
3207
|
}
|
|
3091
3208
|
async set(key, entry) {
|
|
3209
|
+
if (!this.store.has(key) && this.store.size >= this.maxSize) {
|
|
3210
|
+
this.evictExpired();
|
|
3211
|
+
if (this.store.size >= this.maxSize) return;
|
|
3212
|
+
}
|
|
3092
3213
|
this.store.set(key, entry);
|
|
3093
3214
|
}
|
|
3094
3215
|
async increment(key) {
|
|
3095
3216
|
const now = Date.now();
|
|
3096
3217
|
const entry = this.store.get(key);
|
|
3097
3218
|
if (!entry || entry.resetTime < now) {
|
|
3219
|
+
if (this.store.size >= this.maxSize) {
|
|
3220
|
+
this.evictExpired();
|
|
3221
|
+
if (this.store.size >= this.maxSize) return 1;
|
|
3222
|
+
}
|
|
3098
3223
|
this.store.set(key, { count: 1, resetTime: now + this.windowMs });
|
|
3099
3224
|
return 1;
|
|
3100
3225
|
}
|
|
3101
3226
|
entry.count++;
|
|
3102
3227
|
return entry.count;
|
|
3103
3228
|
}
|
|
3229
|
+
/** Eagerly remove expired entries to reclaim capacity. */
|
|
3230
|
+
evictExpired() {
|
|
3231
|
+
const now = Date.now();
|
|
3232
|
+
for (const [key, entry] of this.store.entries()) {
|
|
3233
|
+
if (entry.resetTime < now) this.store.delete(key);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3104
3236
|
async decrement(key) {
|
|
3105
3237
|
const entry = this.store.get(key);
|
|
3106
3238
|
if (entry && entry.count > 0) {
|
|
@@ -3182,6 +3314,6 @@ function createRedisStore(options) {
|
|
|
3182
3314
|
return new RedisStore(options);
|
|
3183
3315
|
}
|
|
3184
3316
|
|
|
3185
|
-
export { ArcisError, ValidationError as ArcisValidationError, BLOCKED, ERRORS, HEADERS, INPUT, InputTooLargeError, MemoryStore, RATE_LIMIT, REDACTION, RateLimitError, RedisStore, SanitizationError, SecurityThreatError, VALIDATION, arcis, arcisWithMethods as arcisFunction, botProtection, createCors, createCsrf, createErrorHandler, createHeaders, createRateLimiter, createRedactor, createRedisStore, createSafeLogger, createSanitizer, createSecureCookies, createSlidingWindowLimiter, createTokenBucketLimiter, createValidator, csrfProtection, main_default as default, detectBot, detectClientIp, detectCommandInjection, detectHeaderInjection, detectJsonpInjection, detectNoSqlInjection, detectPathTraversal, detectPii, detectPrototypePollution, detectSql, detectSsti, detectXss, detectXxe, encodeForAttribute, encodeForCss, encodeForHtml, encodeForJs, encodeForUrl, enforceSecureCookie, errorHandler, fingerprint, formatDuration, generateCsrfToken, isDangerousExtension, isDangerousNoSqlKey, isDangerousProtoKey, isPrivateIp, isRedirectSafe, isUrlSafe, isValidEmailSyntax, parseDuration, rateLimit, redactObjectPii, redactPii, safeCors, safeLog, sanitizeCommand, sanitizeFilename, sanitizeHeaderValue, sanitizeHeaders, sanitizeJsonpCallback, sanitizeObject, sanitizePath, sanitizeSql, sanitizeSsti, sanitizeString, sanitizeXss, sanitizeXxe, scanObjectPii, scanPii, secureCookieDefaults, securityHeaders, validate, validateCsrfToken, validateEmail, validateFile, validateRedirect, validateUrl, verifyEmailMx };
|
|
3317
|
+
export { ArcisError, ValidationError as ArcisValidationError, BLOCKED, ERRORS, HEADERS, INPUT, InputTooLargeError, MemoryStore, RATE_LIMIT, REDACTION, RateLimitError, RedisStore, SanitizationError, SecurityThreatError, VALIDATION, arcis, arcisWithMethods as arcisFunction, botProtection, createCors, createCsrf, createErrorHandler, createHeaders, createHpp, createRateLimiter, createRedactor, createRedisStore, createSafeLogger, createSanitizer, createSecureCookies, createSlidingWindowLimiter, createTokenBucketLimiter, createValidator, csrfProtection, main_default as default, detectBot, detectClientIp, detectCommandInjection, detectHeaderInjection, detectJsonpInjection, detectNoSqlInjection, detectPathTraversal, detectPii, detectPrototypePollution, detectSql, detectSsti, detectXss, detectXxe, encodeForAttribute, encodeForCss, encodeForHtml, encodeForJs, encodeForUrl, enforceSecureCookie, errorHandler, fingerprint, formatDuration, generateCsrfToken, hpp, isDangerousExtension, isDangerousNoSqlKey, isDangerousProtoKey, isPrivateIp, isRedirectSafe, isUrlSafe, isValidEmailSyntax, parseDuration, rateLimit, redactObjectPii, redactPii, safeCors, safeLog, sanitizeCommand, sanitizeFilename, sanitizeHeaderValue, sanitizeHeaders, sanitizeJsonpCallback, sanitizeObject, sanitizePath, sanitizeSql, sanitizeSsti, sanitizeString, sanitizeXss, sanitizeXxe, scanObjectPii, scanPii, secureCookieDefaults, securityHeaders, validate, validateCsrfToken, validateEmail, validateFile, validateRedirect, validateUrl, verifyEmailMx };
|
|
3186
3318
|
//# sourceMappingURL=index.mjs.map
|
|
3187
3319
|
//# sourceMappingURL=index.mjs.map
|