@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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @arcis/node/middleware/csrf
|
|
3
|
+
* CSRF (Cross-Site Request Forgery) protection middleware
|
|
4
|
+
*
|
|
5
|
+
* Implements the double-submit cookie pattern:
|
|
6
|
+
* 1. Server sets a CSRF token in a cookie
|
|
7
|
+
* 2. Client must send the same token in a header or form field
|
|
8
|
+
* 3. Middleware rejects requests where cookie token !== header/field token
|
|
9
|
+
*
|
|
10
|
+
* This works because an attacker's cross-origin form submission will include
|
|
11
|
+
* the cookie automatically, but cannot read it (same-origin policy) to set
|
|
12
|
+
* the matching header.
|
|
13
|
+
*/
|
|
14
|
+
import type { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
15
|
+
/** CSRF protection configuration */
|
|
16
|
+
export interface CsrfOptions {
|
|
17
|
+
/** Cookie name for the CSRF token. Default: '_csrf' */
|
|
18
|
+
cookieName?: string;
|
|
19
|
+
/** Header name to check for the token. Default: 'x-csrf-token' */
|
|
20
|
+
headerName?: string;
|
|
21
|
+
/** Form field name to check for the token. Default: '_csrf' */
|
|
22
|
+
fieldName?: string;
|
|
23
|
+
/** Token byte length (hex-encoded = 2x chars). Default: 32 */
|
|
24
|
+
tokenLength?: number;
|
|
25
|
+
/** HTTP methods to protect. Default: ['POST', 'PUT', 'PATCH', 'DELETE'] */
|
|
26
|
+
protectedMethods?: string[];
|
|
27
|
+
/** Paths to exclude from CSRF checks (e.g., webhook endpoints) */
|
|
28
|
+
excludePaths?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Per-request skip function. If it returns true, CSRF check is skipped
|
|
31
|
+
* for that request. Useful for API key auth or signed webhooks.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* skipCsrf: (req) => Boolean(req.headers['x-api-key'])
|
|
35
|
+
*/
|
|
36
|
+
skipCsrf?: (req: Request) => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Use the __Host- cookie prefix for stronger cookie security.
|
|
39
|
+
* When enabled, the browser enforces: Secure=true, no Domain, Path=/.
|
|
40
|
+
* This prevents CSRF cookie theft across subdomains.
|
|
41
|
+
* Default: false
|
|
42
|
+
*/
|
|
43
|
+
useHostPrefix?: boolean;
|
|
44
|
+
/** Cookie options */
|
|
45
|
+
cookie?: {
|
|
46
|
+
/** Cookie path. Default: '/' */
|
|
47
|
+
path?: string;
|
|
48
|
+
/** HttpOnly — set false so client JS can read it for headers. Default: false */
|
|
49
|
+
httpOnly?: boolean;
|
|
50
|
+
/** Secure flag (HTTPS only). Default: true in production */
|
|
51
|
+
secure?: boolean;
|
|
52
|
+
/** SameSite attribute. Default: 'Lax' */
|
|
53
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
54
|
+
/** Cookie domain */
|
|
55
|
+
domain?: string;
|
|
56
|
+
};
|
|
57
|
+
/** Custom error handler when CSRF validation fails */
|
|
58
|
+
onError?: (req: Request, res: Response, next: NextFunction) => void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate a cryptographically random CSRF token.
|
|
62
|
+
*
|
|
63
|
+
* @param length - Byte length (output is hex, so 2x chars). Default: 32
|
|
64
|
+
* @returns Hex-encoded random token
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* const token = generateCsrfToken(); // 64 hex chars
|
|
68
|
+
*/
|
|
69
|
+
export declare function generateCsrfToken(length?: number): string;
|
|
70
|
+
/**
|
|
71
|
+
* Validate that two CSRF tokens match using constant-time comparison.
|
|
72
|
+
*
|
|
73
|
+
* @param cookieToken - Token from the cookie
|
|
74
|
+
* @param requestToken - Token from the header or form field
|
|
75
|
+
* @returns true if tokens match
|
|
76
|
+
*/
|
|
77
|
+
export declare function validateCsrfToken(cookieToken: string, requestToken: string): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Create CSRF protection middleware using double-submit cookie pattern.
|
|
80
|
+
*
|
|
81
|
+
* For safe methods (GET, HEAD, OPTIONS), sets a CSRF token cookie if not present.
|
|
82
|
+
* For unsafe methods (POST, PUT, PATCH, DELETE), validates the token.
|
|
83
|
+
*
|
|
84
|
+
* @param options - CSRF configuration
|
|
85
|
+
* @returns Express middleware
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* // Basic usage
|
|
89
|
+
* app.use(csrfProtection());
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* // Exclude webhook paths
|
|
93
|
+
* app.use(csrfProtection({
|
|
94
|
+
* excludePaths: ['/api/webhooks/stripe', '/api/webhooks/github']
|
|
95
|
+
* }));
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* // Client-side: read cookie + set header
|
|
99
|
+
* const token = document.cookie.match(/_csrf=([^;]+)/)?.[1];
|
|
100
|
+
* fetch('/api/data', {
|
|
101
|
+
* method: 'POST',
|
|
102
|
+
* headers: { 'X-CSRF-Token': token },
|
|
103
|
+
* credentials: 'same-origin'
|
|
104
|
+
* });
|
|
105
|
+
*/
|
|
106
|
+
export declare function csrfProtection(options?: CsrfOptions): RequestHandler;
|
|
107
|
+
/** Alias for csrfProtection */
|
|
108
|
+
export declare const createCsrf: typeof csrfProtection;
|
|
109
|
+
//# sourceMappingURL=csrf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../../src/middleware/csrf.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,oCAAoC;AACpC,MAAM,WAAW,WAAW;IAC1B,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;IACrC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB;IACrB,MAAM,CAAC,EAAE;QACP,gCAAgC;QAChC,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,gFAAgF;QAChF,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,4DAA4D;QAC5D,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,yCAAyC;QACzC,QAAQ,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;QACrC,oBAAoB;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,sDAAsD;IACtD,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;CACrE;AAUD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM,CAE7D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAQpF;AAsBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,WAAgB,GAAG,cAAc,CAmFxE;AAsDD,+BAA+B;AAC/B,eAAO,MAAM,UAAU,uBAAiB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @arcis/node/middleware/error-handler
|
|
3
|
+
* Production-safe error handler middleware
|
|
4
|
+
*/
|
|
5
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
6
|
+
import type { ErrorHandlerOptions } from '../core/types';
|
|
7
|
+
/**
|
|
8
|
+
* Check if an error message contains sensitive infrastructure details.
|
|
9
|
+
*/
|
|
10
|
+
export declare function containsSensitiveInfo(message: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Create Express error handler that hides sensitive details in production.
|
|
13
|
+
*
|
|
14
|
+
* Prevents information leakage by:
|
|
15
|
+
* - Hiding stack traces in production
|
|
16
|
+
* - Hiding error messages unless explicitly exposed
|
|
17
|
+
* - Scrubbing database errors, connection strings, and internal IPs
|
|
18
|
+
*
|
|
19
|
+
* @param options - Error handler configuration (or boolean for isDev)
|
|
20
|
+
* @returns Express error handling middleware
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* // Production mode (default) - hides error details
|
|
24
|
+
* app.use(errorHandler());
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Development mode - shows error details and stack traces
|
|
28
|
+
* app.use(errorHandler({ isDev: true }));
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // With custom logger
|
|
32
|
+
* app.use(errorHandler({
|
|
33
|
+
* isDev: false,
|
|
34
|
+
* logger: arcis.logger()
|
|
35
|
+
* }));
|
|
36
|
+
*/
|
|
37
|
+
export declare function errorHandler(options?: ErrorHandlerOptions | boolean): (err: Error, req: Request, res: Response, next: NextFunction) => void;
|
|
38
|
+
/**
|
|
39
|
+
* Alias for errorHandler
|
|
40
|
+
* @see errorHandler
|
|
41
|
+
*/
|
|
42
|
+
export declare const createErrorHandler: typeof errorHandler;
|
|
43
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE/D,OAAO,KAAK,EAAE,mBAAmB,EAAa,MAAM,eAAe,CAAC;AAyBpE;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAmB,GAAG,OAAe,GAC7C,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CA2DvE;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,qBAAe,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @arcis/node/middleware/headers
|
|
3
|
+
* Security headers middleware
|
|
4
|
+
*/
|
|
5
|
+
import type { RequestHandler } from 'express';
|
|
6
|
+
import type { HeaderOptions } from '../core/types';
|
|
7
|
+
/**
|
|
8
|
+
* Create Express middleware for security headers.
|
|
9
|
+
* Sets CSP, HSTS, X-Frame-Options, and other security headers.
|
|
10
|
+
*
|
|
11
|
+
* @param options - Header configuration
|
|
12
|
+
* @returns Express middleware
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* app.use(createHeaders());
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* app.use(createHeaders({
|
|
19
|
+
* frameOptions: 'SAMEORIGIN',
|
|
20
|
+
* contentSecurityPolicy: "default-src 'self'"
|
|
21
|
+
* }));
|
|
22
|
+
*/
|
|
23
|
+
export declare function createHeaders(options?: HeaderOptions): RequestHandler;
|
|
24
|
+
/**
|
|
25
|
+
* Alias for createHeaders
|
|
26
|
+
* @see createHeaders
|
|
27
|
+
*/
|
|
28
|
+
export declare const securityHeaders: typeof createHeaders;
|
|
29
|
+
//# sourceMappingURL=headers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/middleware/headers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,eAAe,CAAC;AAEhE;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,cAAc,CAwHzE;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,sBAAgB,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @arcis/node/middleware/hpp
|
|
3
|
+
* HTTP Parameter Pollution (HPP) protection middleware
|
|
4
|
+
*
|
|
5
|
+
* Normalizes duplicate query and body parameters to their last value,
|
|
6
|
+
* preventing attackers from bypassing validation by repeating parameters.
|
|
7
|
+
*
|
|
8
|
+
* Attack example:
|
|
9
|
+
* GET /search?role=user&role=admin
|
|
10
|
+
* Without HPP: req.query.role = ['user', 'admin']
|
|
11
|
+
* With HPP: req.query.role = 'admin' (last value wins)
|
|
12
|
+
*
|
|
13
|
+
* Originals are preserved in req.queryPolluted / req.bodyPolluted
|
|
14
|
+
* for logging or auditing without blocking the request.
|
|
15
|
+
*/
|
|
16
|
+
import type { RequestHandler } from 'express';
|
|
17
|
+
/** HPP protection configuration */
|
|
18
|
+
export interface HppOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Parameters that legitimately accept arrays and should not be normalized.
|
|
21
|
+
* Example: ['tags', 'ids', 'filter']
|
|
22
|
+
*/
|
|
23
|
+
whitelist?: string[];
|
|
24
|
+
/** Normalize duplicate query string parameters. Default: true */
|
|
25
|
+
checkQuery?: boolean;
|
|
26
|
+
/** Normalize duplicate body parameters. Default: true */
|
|
27
|
+
checkBody?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* HTTP Parameter Pollution protection middleware.
|
|
31
|
+
*
|
|
32
|
+
* Normalizes duplicate query/body parameters to a single value (last wins).
|
|
33
|
+
* Whitelisted parameters are allowed to remain as arrays.
|
|
34
|
+
*
|
|
35
|
+
* @param options - HPP configuration
|
|
36
|
+
* @returns Express middleware
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Basic — normalize all duplicates
|
|
40
|
+
* app.use(hpp());
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* // Allow arrays for specific params (e.g., tag filters, IDs)
|
|
44
|
+
* app.use(hpp({ whitelist: ['tags', 'ids'] }));
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* // Inspect what was removed (for logging)
|
|
48
|
+
* app.use((req, res, next) => {
|
|
49
|
+
* const polluted = (req as any).queryPolluted;
|
|
50
|
+
* if (Object.keys(polluted).length) logger.warn('HPP detected', polluted);
|
|
51
|
+
* next();
|
|
52
|
+
* });
|
|
53
|
+
*/
|
|
54
|
+
export declare function hpp(options?: HppOptions): RequestHandler;
|
|
55
|
+
export declare const createHpp: typeof hpp;
|
|
56
|
+
//# sourceMappingURL=hpp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hpp.d.ts","sourceRoot":"","sources":["../../src/middleware/hpp.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,mCAAmC;AACnC,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yDAAyD;IACzD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,UAAe,GAAG,cAAc,CAwD5D;AAED,eAAO,MAAM,SAAS,YAAM,CAAC"}
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @module @arcis/node/middleware
|
|
3
|
+
* All middleware for Arcis
|
|
4
|
+
*/
|
|
5
|
+
export { arcis, arcisFunction } from './main';
|
|
6
|
+
export { default } from './main';
|
|
7
|
+
export { createRateLimiter, rateLimit } from './rate-limit';
|
|
8
|
+
export { createSlidingWindowLimiter } from './rate-limit-sliding';
|
|
9
|
+
export { createTokenBucketLimiter } from './rate-limit-token';
|
|
10
|
+
export { createHeaders, securityHeaders } from './headers';
|
|
11
|
+
export { errorHandler, createErrorHandler } from './error-handler';
|
|
12
|
+
export { safeCors, createCors } from './cors';
|
|
13
|
+
export { secureCookieDefaults, createSecureCookies, enforceSecureCookie } from './cookies';
|
|
14
|
+
export { botProtection, detectBot } from './bot-detection';
|
|
15
|
+
export { csrfProtection, createCsrf, generateCsrfToken, validateCsrfToken } from './csrf';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAGjC,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC3F,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC"}
|
package/dist/middleware/index.js
CHANGED
|
@@ -69,7 +69,15 @@ var XSS_REMOVE_PATTERNS = [
|
|
|
69
69
|
/javascript\s*:/gi,
|
|
70
70
|
/vbscript\s*:/gi,
|
|
71
71
|
/** data: URIs with HTML/script content */
|
|
72
|
-
/data\s*:\s*text\/html[^>\s]*/gi
|
|
72
|
+
/data\s*:\s*text\/html[^>\s]*/gi,
|
|
73
|
+
/** form tag injection — phishing via action= redirection */
|
|
74
|
+
/<form[\s>][^>]*/gi,
|
|
75
|
+
/** meta tag injection — http-equiv refresh or CSP bypass */
|
|
76
|
+
/<meta[\s>][^>]*/gi,
|
|
77
|
+
/** base href hijacking */
|
|
78
|
+
/<base[\s>][^>]*/gi,
|
|
79
|
+
/** link tag injection — stylesheet or preload attacks */
|
|
80
|
+
/<link[\s>][^>]*/gi
|
|
73
81
|
];
|
|
74
82
|
var SQL_PATTERNS = [
|
|
75
83
|
/** SQL keywords */
|
|
@@ -133,8 +141,8 @@ var COMMAND_PATTERNS = [
|
|
|
133
141
|
/[;&|`]/g,
|
|
134
142
|
/** Command substitution: $( ... ) — matched as a pair to reduce false positives */
|
|
135
143
|
/\$\(/g,
|
|
136
|
-
/** URL-encoded
|
|
137
|
-
/%0[
|
|
144
|
+
/** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
|
|
145
|
+
/%0[0-9a-f]/gi
|
|
138
146
|
];
|
|
139
147
|
var DANGEROUS_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
140
148
|
"__proto__",
|
|
@@ -422,7 +430,24 @@ function createRateLimiter(options = {}) {
|
|
|
422
430
|
}
|
|
423
431
|
next();
|
|
424
432
|
} catch (error) {
|
|
425
|
-
console.error("[arcis] Rate limiter error:", error);
|
|
433
|
+
console.error("[arcis] Rate limiter store error, using in-memory fallback:", error);
|
|
434
|
+
try {
|
|
435
|
+
const key = keyGenerator(req);
|
|
436
|
+
const now = Date.now();
|
|
437
|
+
if (!inMemoryStore[key] || inMemoryStore[key].resetTime < now) {
|
|
438
|
+
inMemoryStore[key] = { count: 1, resetTime: now + windowMs };
|
|
439
|
+
} else {
|
|
440
|
+
inMemoryStore[key].count++;
|
|
441
|
+
}
|
|
442
|
+
const count = inMemoryStore[key].count;
|
|
443
|
+
if (count > max) {
|
|
444
|
+
const resetSeconds = Math.ceil((inMemoryStore[key].resetTime - now) / 1e3);
|
|
445
|
+
res.setHeader("Retry-After", resetSeconds.toString());
|
|
446
|
+
res.status(statusCode).json({ error: message, retryAfter: resetSeconds });
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
} catch {
|
|
450
|
+
}
|
|
426
451
|
next();
|
|
427
452
|
}
|
|
428
453
|
};
|
|
@@ -632,26 +657,31 @@ function sanitizePath(input, collectThreats = false) {
|
|
|
632
657
|
const threats = [];
|
|
633
658
|
let value = input;
|
|
634
659
|
let wasSanitized = false;
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
660
|
+
value = value.normalize("NFKC");
|
|
661
|
+
let prev;
|
|
662
|
+
do {
|
|
663
|
+
prev = value;
|
|
664
|
+
for (const pattern of PATH_PATTERNS) {
|
|
638
665
|
pattern.lastIndex = 0;
|
|
639
|
-
if (
|
|
640
|
-
|
|
641
|
-
if (
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
666
|
+
if (pattern.test(value)) {
|
|
667
|
+
pattern.lastIndex = 0;
|
|
668
|
+
if (collectThreats) {
|
|
669
|
+
const matches = value.match(pattern);
|
|
670
|
+
if (matches) {
|
|
671
|
+
for (const match of matches) {
|
|
672
|
+
threats.push({
|
|
673
|
+
type: "path_traversal",
|
|
674
|
+
pattern: pattern.source,
|
|
675
|
+
original: match
|
|
676
|
+
});
|
|
677
|
+
}
|
|
648
678
|
}
|
|
649
679
|
}
|
|
680
|
+
value = value.replace(pattern, "");
|
|
681
|
+
wasSanitized = true;
|
|
650
682
|
}
|
|
651
|
-
value = value.replace(pattern, "");
|
|
652
|
-
wasSanitized = true;
|
|
653
683
|
}
|
|
654
|
-
}
|
|
684
|
+
} while (value !== prev);
|
|
655
685
|
if (collectThreats) {
|
|
656
686
|
return { value, wasSanitized, threats };
|
|
657
687
|
}
|
|
@@ -709,7 +739,7 @@ function sanitizeString(value, options = {}) {
|
|
|
709
739
|
if (value.length > maxSize) {
|
|
710
740
|
throw new InputTooLargeError(maxSize, value.length);
|
|
711
741
|
}
|
|
712
|
-
const reject = options.mode
|
|
742
|
+
const reject = options.mode === "reject";
|
|
713
743
|
let result = value;
|
|
714
744
|
if (options.sql !== false) {
|
|
715
745
|
if (reject) {
|
|
@@ -1586,11 +1616,9 @@ function generateCsrfToken(length = 32) {
|
|
|
1586
1616
|
function validateCsrfToken(cookieToken, requestToken) {
|
|
1587
1617
|
if (!cookieToken || !requestToken) return false;
|
|
1588
1618
|
if (cookieToken.length !== requestToken.length) return false;
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
}
|
|
1593
|
-
return result === 0;
|
|
1619
|
+
const a = Buffer.from(cookieToken);
|
|
1620
|
+
const b = Buffer.from(requestToken);
|
|
1621
|
+
return crypto.timingSafeEqual(a, b);
|
|
1594
1622
|
}
|
|
1595
1623
|
function getRequestToken(req, headerName, fieldName) {
|
|
1596
1624
|
const headerToken = req.headers[headerName.toLowerCase()];
|
|
@@ -1599,19 +1627,17 @@ function getRequestToken(req, headerName, fieldName) {
|
|
|
1599
1627
|
const bodyToken = req.body[fieldName];
|
|
1600
1628
|
if (typeof bodyToken === "string" && bodyToken) return bodyToken;
|
|
1601
1629
|
}
|
|
1602
|
-
if (req.query && fieldName in req.query) {
|
|
1603
|
-
const queryToken = req.query[fieldName];
|
|
1604
|
-
if (typeof queryToken === "string" && queryToken) return queryToken;
|
|
1605
|
-
}
|
|
1606
1630
|
return void 0;
|
|
1607
1631
|
}
|
|
1608
1632
|
function csrfProtection(options = {}) {
|
|
1609
|
-
const
|
|
1633
|
+
const baseCookieName = options.cookieName ?? DEFAULTS.cookieName;
|
|
1634
|
+
const cookieName = options.useHostPrefix ? `__Host-${baseCookieName}` : baseCookieName;
|
|
1610
1635
|
const headerName = options.headerName ?? DEFAULTS.headerName;
|
|
1611
1636
|
const fieldName = options.fieldName ?? DEFAULTS.fieldName;
|
|
1612
1637
|
const tokenLength = options.tokenLength ?? DEFAULTS.tokenLength;
|
|
1613
1638
|
const protectedMethods = options.protectedMethods ?? [...DEFAULTS.protectedMethods];
|
|
1614
1639
|
const excludePaths = options.excludePaths ?? [];
|
|
1640
|
+
const skipCsrf = options.skipCsrf;
|
|
1615
1641
|
const isProduction = process.env.NODE_ENV === "production";
|
|
1616
1642
|
const cookieOpts = {
|
|
1617
1643
|
path: options.cookie?.path ?? "/",
|
|
@@ -1631,6 +1657,9 @@ function csrfProtection(options = {}) {
|
|
|
1631
1657
|
const protectedSet = new Set(protectedMethods.map((m) => m.toUpperCase()));
|
|
1632
1658
|
return (req, res, next) => {
|
|
1633
1659
|
const method = req.method.toUpperCase();
|
|
1660
|
+
if (skipCsrf && skipCsrf(req)) {
|
|
1661
|
+
return next();
|
|
1662
|
+
}
|
|
1634
1663
|
const requestPath = req.path || req.url;
|
|
1635
1664
|
if (excludePaths.some((p) => requestPath === p || requestPath.startsWith(p + "/"))) {
|
|
1636
1665
|
return next();
|
|
@@ -1680,7 +1709,15 @@ function setCsrfCookie(res, name, token, opts) {
|
|
|
1680
1709
|
if (opts.secure) parts.push("Secure");
|
|
1681
1710
|
parts.push(`SameSite=${opts.sameSite}`);
|
|
1682
1711
|
if (opts.domain) parts.push(`Domain=${opts.domain}`);
|
|
1683
|
-
|
|
1712
|
+
const newCookie = parts.join("; ");
|
|
1713
|
+
const existing = res.getHeader("Set-Cookie");
|
|
1714
|
+
if (existing === void 0) {
|
|
1715
|
+
res.setHeader("Set-Cookie", newCookie);
|
|
1716
|
+
} else if (Array.isArray(existing)) {
|
|
1717
|
+
res.setHeader("Set-Cookie", [...existing, newCookie]);
|
|
1718
|
+
} else {
|
|
1719
|
+
res.setHeader("Set-Cookie", [existing, newCookie]);
|
|
1720
|
+
}
|
|
1684
1721
|
}
|
|
1685
1722
|
function escapeRegex(str) {
|
|
1686
1723
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|