@arcis/node 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -211
- package/dist/core/index.d.mts +4 -4
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +13 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +13 -2
- package/dist/core/index.mjs.map +1 -1
- package/dist/{headers-DBQedhrb.d.mts → encode-CrQCGlBq.d.mts} +202 -2
- package/dist/{headers-BJq2OA0i.d.ts → encode-jl9sOwmA.d.ts} +202 -2
- package/dist/{index-BvcFpoR3.d.ts → index-BAhgn9V2.d.ts} +96 -2
- package/dist/{index-iCOw8Fcg.d.ts → index-BGNKspqH.d.ts} +1 -1
- package/dist/{index-CslcoZUN.d.mts → index-Cd02z-0j.d.mts} +1 -1
- package/dist/{index-CCcPuTBo.d.mts → index-DgJtWMSj.d.mts} +96 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +647 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +629 -9
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.d.mts +1 -1
- package/dist/logging/index.d.ts +1 -1
- package/dist/logging/index.js +12 -1
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs +12 -1
- package/dist/logging/index.mjs.map +1 -1
- package/dist/middleware/index.d.mts +2 -2
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.js +168 -6
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +165 -7
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/sanitizers/index.d.mts +2 -2
- package/dist/sanitizers/index.d.ts +2 -2
- package/dist/sanitizers/index.js +403 -3
- package/dist/sanitizers/index.js.map +1 -1
- package/dist/sanitizers/index.mjs +388 -4
- package/dist/sanitizers/index.mjs.map +1 -1
- package/dist/stores/index.d.mts +1 -1
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/index.mjs.map +1 -1
- package/dist/{types-BOdL3ZWo.d.mts → types-BOkx5YJc.d.mts} +17 -2
- package/dist/{types-BOdL3ZWo.d.ts → types-BOkx5YJc.d.ts} +17 -2
- package/dist/validation/index.d.mts +2 -2
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +105 -3
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/index.mjs +105 -3
- package/dist/validation/index.mjs.map +1 -1
- package/package.json +114 -114
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
|
|
1
3
|
// src/core/constants.ts
|
|
2
4
|
var INPUT = {
|
|
3
5
|
/** Default maximum input size (1MB) */
|
|
@@ -85,7 +87,11 @@ var SQL_PATTERNS = [
|
|
|
85
87
|
/** Time-based blind: SLEEP() */
|
|
86
88
|
/\bSLEEP\s*\(\s*\d+\s*\)/gi,
|
|
87
89
|
/** Time-based blind: BENCHMARK() */
|
|
88
|
-
/\bBENCHMARK\s*\(/gi
|
|
90
|
+
/\bBENCHMARK\s*\(/gi,
|
|
91
|
+
/** Time-based blind: PostgreSQL pg_sleep() */
|
|
92
|
+
/\bpg_sleep\s*\(/gi,
|
|
93
|
+
/** Time-based blind: MSSQL WAITFOR DELAY */
|
|
94
|
+
/\bWAITFOR\s+DELAY\b/gi
|
|
89
95
|
];
|
|
90
96
|
var PATH_PATTERNS = [
|
|
91
97
|
/** Unix path traversal */
|
|
@@ -103,6 +109,10 @@ var PATH_PATTERNS = [
|
|
|
103
109
|
/\.%2e[\\/]/gi,
|
|
104
110
|
/** Fully URL-encoded: %2e%2e%2f */
|
|
105
111
|
/%2e%2e%2f/gi,
|
|
112
|
+
/** Double URL-encoded forward slash: %252f */
|
|
113
|
+
/%252f/gi,
|
|
114
|
+
/** Dotdotslash bypass: ....// or ....\\ */
|
|
115
|
+
/\.{2,}[/\\]{2,}/g,
|
|
106
116
|
/** Null byte injection in paths */
|
|
107
117
|
/\0/g
|
|
108
118
|
];
|
|
@@ -118,7 +128,9 @@ var COMMAND_PATTERNS = [
|
|
|
118
128
|
*/
|
|
119
129
|
/[;&|`]/g,
|
|
120
130
|
/** Command substitution: $( ... ) — matched as a pair to reduce false positives */
|
|
121
|
-
/\$\(/g
|
|
131
|
+
/\$\(/g,
|
|
132
|
+
/** URL-encoded newline/carriage-return injection (%0a, %0d) */
|
|
133
|
+
/%0[ad]/gi
|
|
122
134
|
];
|
|
123
135
|
var DANGEROUS_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
124
136
|
"__proto__",
|
|
@@ -152,6 +164,7 @@ var NOSQL_DANGEROUS_KEYS = /* @__PURE__ */ new Set([
|
|
|
152
164
|
"$expr",
|
|
153
165
|
"$mod",
|
|
154
166
|
"$text",
|
|
167
|
+
"$jsonSchema",
|
|
155
168
|
// Array
|
|
156
169
|
"$elemMatch",
|
|
157
170
|
"$all",
|
|
@@ -259,7 +272,12 @@ function createHeaders(options = {}) {
|
|
|
259
272
|
hsts = true,
|
|
260
273
|
referrerPolicy = HEADERS.REFERRER_POLICY,
|
|
261
274
|
permissionsPolicy = HEADERS.PERMISSIONS_POLICY,
|
|
262
|
-
cacheControl = true
|
|
275
|
+
cacheControl = true,
|
|
276
|
+
crossOriginOpenerPolicy = "same-origin",
|
|
277
|
+
crossOriginResourcePolicy = "same-origin",
|
|
278
|
+
crossOriginEmbedderPolicy = "require-corp",
|
|
279
|
+
originAgentCluster = true,
|
|
280
|
+
dnsPrefetchControl = true
|
|
263
281
|
} = options;
|
|
264
282
|
return (req, res, next) => {
|
|
265
283
|
if (contentSecurityPolicy) {
|
|
@@ -267,7 +285,7 @@ function createHeaders(options = {}) {
|
|
|
267
285
|
res.setHeader("Content-Security-Policy", csp);
|
|
268
286
|
}
|
|
269
287
|
if (xssFilter) {
|
|
270
|
-
res.setHeader("X-XSS-Protection", "
|
|
288
|
+
res.setHeader("X-XSS-Protection", "0");
|
|
271
289
|
}
|
|
272
290
|
if (noSniff) {
|
|
273
291
|
res.setHeader("X-Content-Type-Options", HEADERS.CONTENT_TYPE_OPTIONS);
|
|
@@ -294,6 +312,21 @@ function createHeaders(options = {}) {
|
|
|
294
312
|
if (permissionsPolicy) {
|
|
295
313
|
res.setHeader("Permissions-Policy", permissionsPolicy);
|
|
296
314
|
}
|
|
315
|
+
if (crossOriginOpenerPolicy) {
|
|
316
|
+
res.setHeader("Cross-Origin-Opener-Policy", crossOriginOpenerPolicy);
|
|
317
|
+
}
|
|
318
|
+
if (crossOriginResourcePolicy) {
|
|
319
|
+
res.setHeader("Cross-Origin-Resource-Policy", crossOriginResourcePolicy);
|
|
320
|
+
}
|
|
321
|
+
if (crossOriginEmbedderPolicy) {
|
|
322
|
+
res.setHeader("Cross-Origin-Embedder-Policy", crossOriginEmbedderPolicy);
|
|
323
|
+
}
|
|
324
|
+
if (originAgentCluster) {
|
|
325
|
+
res.setHeader("Origin-Agent-Cluster", "?1");
|
|
326
|
+
}
|
|
327
|
+
if (dnsPrefetchControl) {
|
|
328
|
+
res.setHeader("X-DNS-Prefetch-Control", "off");
|
|
329
|
+
}
|
|
297
330
|
res.setHeader("X-Permitted-Cross-Domain-Policies", "none");
|
|
298
331
|
if (cacheControl) {
|
|
299
332
|
const cacheControlValue = typeof cacheControl === "string" ? cacheControl : HEADERS.CACHE_CONTROL;
|
|
@@ -705,7 +738,8 @@ function sanitizeObject(obj, options = {}) {
|
|
|
705
738
|
if (typeof obj === "string") return sanitizeString(obj, options);
|
|
706
739
|
if (typeof obj !== "object") return obj;
|
|
707
740
|
if (Array.isArray(obj)) return obj.map((item) => sanitizeObject(item, options));
|
|
708
|
-
|
|
741
|
+
const result = sanitizeObjectDepth(obj, options, 0);
|
|
742
|
+
return options.freeze ? Object.freeze(result) : result;
|
|
709
743
|
}
|
|
710
744
|
function sanitizeObjectDepth(obj, options, depth) {
|
|
711
745
|
if (depth >= INPUT.MAX_RECURSION_DEPTH) return obj;
|
|
@@ -926,12 +960,21 @@ function validateField(field, value, rules) {
|
|
|
926
960
|
"audio/mpeg": [Buffer.from([255, 251]), Buffer.from([255, 243]), Buffer.from([73, 68, 51])]});
|
|
927
961
|
|
|
928
962
|
// src/logging/redactor.ts
|
|
963
|
+
var LOG_LEVELS = {
|
|
964
|
+
debug: 0,
|
|
965
|
+
info: 1,
|
|
966
|
+
warn: 2,
|
|
967
|
+
error: 3,
|
|
968
|
+
silent: 4
|
|
969
|
+
};
|
|
929
970
|
function createSafeLogger(options = {}) {
|
|
930
971
|
const {
|
|
931
972
|
redactKeys = [],
|
|
932
973
|
maxLength = REDACTION.DEFAULT_MAX_LENGTH,
|
|
933
|
-
redactPatterns = []
|
|
974
|
+
redactPatterns = [],
|
|
975
|
+
level: minLevel = "debug"
|
|
934
976
|
} = options;
|
|
977
|
+
const minLevelNum = LOG_LEVELS[minLevel] ?? 0;
|
|
935
978
|
const allRedactKeys = /* @__PURE__ */ new Set([
|
|
936
979
|
...Array.from(REDACTION.SENSITIVE_KEYS),
|
|
937
980
|
...redactKeys.map((k) => k.toLowerCase())
|
|
@@ -957,6 +1000,8 @@ function createSafeLogger(options = {}) {
|
|
|
957
1000
|
return result;
|
|
958
1001
|
}
|
|
959
1002
|
function log(level, message, data) {
|
|
1003
|
+
const levelNum = LOG_LEVELS[level] ?? 0;
|
|
1004
|
+
if (levelNum < minLevelNum) return;
|
|
960
1005
|
const entry = {
|
|
961
1006
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
962
1007
|
level,
|
|
@@ -1524,7 +1569,120 @@ function botProtection(options = {}) {
|
|
|
1524
1569
|
next();
|
|
1525
1570
|
};
|
|
1526
1571
|
}
|
|
1572
|
+
var DEFAULTS = {
|
|
1573
|
+
cookieName: "_csrf",
|
|
1574
|
+
headerName: "x-csrf-token",
|
|
1575
|
+
fieldName: "_csrf",
|
|
1576
|
+
tokenLength: 32,
|
|
1577
|
+
protectedMethods: ["POST", "PUT", "PATCH", "DELETE"]
|
|
1578
|
+
};
|
|
1579
|
+
function generateCsrfToken(length = 32) {
|
|
1580
|
+
return randomBytes(length).toString("hex");
|
|
1581
|
+
}
|
|
1582
|
+
function validateCsrfToken(cookieToken, requestToken) {
|
|
1583
|
+
if (!cookieToken || !requestToken) return false;
|
|
1584
|
+
if (cookieToken.length !== requestToken.length) return false;
|
|
1585
|
+
let result = 0;
|
|
1586
|
+
for (let i = 0; i < cookieToken.length; i++) {
|
|
1587
|
+
result |= cookieToken.charCodeAt(i) ^ requestToken.charCodeAt(i);
|
|
1588
|
+
}
|
|
1589
|
+
return result === 0;
|
|
1590
|
+
}
|
|
1591
|
+
function getRequestToken(req, headerName, fieldName) {
|
|
1592
|
+
const headerToken = req.headers[headerName.toLowerCase()];
|
|
1593
|
+
if (typeof headerToken === "string" && headerToken) return headerToken;
|
|
1594
|
+
if (req.body && typeof req.body === "object" && fieldName in req.body) {
|
|
1595
|
+
const bodyToken = req.body[fieldName];
|
|
1596
|
+
if (typeof bodyToken === "string" && bodyToken) return bodyToken;
|
|
1597
|
+
}
|
|
1598
|
+
if (req.query && fieldName in req.query) {
|
|
1599
|
+
const queryToken = req.query[fieldName];
|
|
1600
|
+
if (typeof queryToken === "string" && queryToken) return queryToken;
|
|
1601
|
+
}
|
|
1602
|
+
return void 0;
|
|
1603
|
+
}
|
|
1604
|
+
function csrfProtection(options = {}) {
|
|
1605
|
+
const cookieName = options.cookieName ?? DEFAULTS.cookieName;
|
|
1606
|
+
const headerName = options.headerName ?? DEFAULTS.headerName;
|
|
1607
|
+
const fieldName = options.fieldName ?? DEFAULTS.fieldName;
|
|
1608
|
+
const tokenLength = options.tokenLength ?? DEFAULTS.tokenLength;
|
|
1609
|
+
const protectedMethods = options.protectedMethods ?? [...DEFAULTS.protectedMethods];
|
|
1610
|
+
const excludePaths = options.excludePaths ?? [];
|
|
1611
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
1612
|
+
const cookieOpts = {
|
|
1613
|
+
path: options.cookie?.path ?? "/",
|
|
1614
|
+
httpOnly: options.cookie?.httpOnly ?? false,
|
|
1615
|
+
// Must be readable by client JS
|
|
1616
|
+
secure: options.cookie?.secure ?? isProduction,
|
|
1617
|
+
sameSite: options.cookie?.sameSite ?? "Lax",
|
|
1618
|
+
domain: options.cookie?.domain
|
|
1619
|
+
};
|
|
1620
|
+
const defaultOnError = (_req, res, _next) => {
|
|
1621
|
+
res.status(403).json({
|
|
1622
|
+
error: "CSRF token validation failed",
|
|
1623
|
+
message: "Invalid or missing CSRF token. Include the token from the cookie in the X-CSRF-Token header."
|
|
1624
|
+
});
|
|
1625
|
+
};
|
|
1626
|
+
const onError = options.onError ?? defaultOnError;
|
|
1627
|
+
const protectedSet = new Set(protectedMethods.map((m) => m.toUpperCase()));
|
|
1628
|
+
return (req, res, next) => {
|
|
1629
|
+
const method = req.method.toUpperCase();
|
|
1630
|
+
const requestPath = req.path || req.url;
|
|
1631
|
+
if (excludePaths.some((p) => requestPath === p || requestPath.startsWith(p + "/"))) {
|
|
1632
|
+
return next();
|
|
1633
|
+
}
|
|
1634
|
+
req.csrfToken = () => {
|
|
1635
|
+
const existing = getCookieValue(req, cookieName);
|
|
1636
|
+
if (existing) return existing;
|
|
1637
|
+
const token = generateCsrfToken(tokenLength);
|
|
1638
|
+
setCsrfCookie(res, cookieName, token, cookieOpts);
|
|
1639
|
+
return token;
|
|
1640
|
+
};
|
|
1641
|
+
if (!protectedSet.has(method)) {
|
|
1642
|
+
const existing = getCookieValue(req, cookieName);
|
|
1643
|
+
if (!existing) {
|
|
1644
|
+
const token = generateCsrfToken(tokenLength);
|
|
1645
|
+
setCsrfCookie(res, cookieName, token, cookieOpts);
|
|
1646
|
+
}
|
|
1647
|
+
return next();
|
|
1648
|
+
}
|
|
1649
|
+
const cookieToken = getCookieValue(req, cookieName);
|
|
1650
|
+
if (!cookieToken) {
|
|
1651
|
+
return onError(req, res, next);
|
|
1652
|
+
}
|
|
1653
|
+
const requestToken = getRequestToken(req, headerName, fieldName);
|
|
1654
|
+
if (!requestToken) {
|
|
1655
|
+
return onError(req, res, next);
|
|
1656
|
+
}
|
|
1657
|
+
if (!validateCsrfToken(cookieToken, requestToken)) {
|
|
1658
|
+
return onError(req, res, next);
|
|
1659
|
+
}
|
|
1660
|
+
next();
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
function getCookieValue(req, name) {
|
|
1664
|
+
if (req.cookies && typeof req.cookies === "object" && name in req.cookies) {
|
|
1665
|
+
return req.cookies[name];
|
|
1666
|
+
}
|
|
1667
|
+
const cookieHeader = req.headers.cookie;
|
|
1668
|
+
if (!cookieHeader) return void 0;
|
|
1669
|
+
const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${escapeRegex(name)}=([^;]*)`));
|
|
1670
|
+
return match ? decodeURIComponent(match[1]) : void 0;
|
|
1671
|
+
}
|
|
1672
|
+
function setCsrfCookie(res, name, token, opts) {
|
|
1673
|
+
const parts = [`${name}=${token}`];
|
|
1674
|
+
parts.push(`Path=${opts.path}`);
|
|
1675
|
+
if (opts.httpOnly) parts.push("HttpOnly");
|
|
1676
|
+
if (opts.secure) parts.push("Secure");
|
|
1677
|
+
parts.push(`SameSite=${opts.sameSite}`);
|
|
1678
|
+
if (opts.domain) parts.push(`Domain=${opts.domain}`);
|
|
1679
|
+
res.setHeader("Set-Cookie", parts.join("; "));
|
|
1680
|
+
}
|
|
1681
|
+
function escapeRegex(str) {
|
|
1682
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1683
|
+
}
|
|
1684
|
+
var createCsrf = csrfProtection;
|
|
1527
1685
|
|
|
1528
|
-
export { arcis, arcisWithMethods as arcisFunction, botProtection, createCors, createErrorHandler, createHeaders, createRateLimiter, createSecureCookies, createSlidingWindowLimiter, createTokenBucketLimiter, main_default as default, detectBot, enforceSecureCookie, errorHandler, rateLimit, safeCors, secureCookieDefaults, securityHeaders };
|
|
1686
|
+
export { arcis, arcisWithMethods as arcisFunction, botProtection, createCors, createCsrf, createErrorHandler, createHeaders, createRateLimiter, createSecureCookies, createSlidingWindowLimiter, createTokenBucketLimiter, csrfProtection, main_default as default, detectBot, enforceSecureCookie, errorHandler, generateCsrfToken, rateLimit, safeCors, secureCookieDefaults, securityHeaders, validateCsrfToken };
|
|
1529
1687
|
//# sourceMappingURL=index.mjs.map
|
|
1530
1688
|
//# sourceMappingURL=index.mjs.map
|