@arcis/node 1.4.0 → 1.4.3
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/constants.d.ts +2 -2
- package/dist/core/constants.d.ts.map +1 -1
- 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/core/types.d.ts +6 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +527 -63
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +525 -65
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs.map +1 -1
- package/dist/middleware/bot-detection.d.ts.map +1 -1
- package/dist/middleware/cookies.d.ts.map +1 -1
- package/dist/middleware/csrf.d.ts +10 -0
- package/dist/middleware/csrf.d.ts.map +1 -1
- package/dist/middleware/hpp.d.ts.map +1 -1
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +671 -39
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +671 -41
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/middleware/main.d.ts.map +1 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/signup-protection.d.ts +65 -0
- package/dist/middleware/signup-protection.d.ts.map +1 -0
- package/dist/middleware/telemetry.d.ts +36 -0
- package/dist/middleware/telemetry.d.ts.map +1 -0
- package/dist/sanitizers/encode.d.ts.map +1 -1
- package/dist/sanitizers/index.d.ts +1 -0
- package/dist/sanitizers/index.d.ts.map +1 -1
- package/dist/sanitizers/index.js +113 -37
- package/dist/sanitizers/index.js.map +1 -1
- package/dist/sanitizers/index.mjs +111 -38
- package/dist/sanitizers/index.mjs.map +1 -1
- package/dist/sanitizers/ldap.d.ts +42 -0
- package/dist/sanitizers/ldap.d.ts.map +1 -0
- package/dist/sanitizers/path.d.ts.map +1 -1
- package/dist/sanitizers/pii.d.ts.map +1 -1
- package/dist/sanitizers/sanitize.d.ts.map +1 -1
- package/dist/sanitizers/ssti.d.ts.map +1 -1
- package/dist/sanitizers/xxe.d.ts.map +1 -1
- 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 +4 -10
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/telemetry/client.d.ts +60 -0
- package/dist/telemetry/client.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +3 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/types.d.ts +59 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/validation/index.js +41 -21
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/index.mjs +41 -21
- package/dist/validation/index.mjs.map +1 -1
- package/package.json +8 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/middleware/main.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,oBAAoB,EAIrB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/middleware/main.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,oBAAoB,EAIrB,MAAM,eAAe,CAAC;AAwCvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,oBAAoB,CAyDtE;AAGD,QAAA,MAAM,gBAAgB,EAAY,aAAa,CAAC;AAQhD,OAAO,EAAE,gBAAgB,IAAI,aAAa,EAAE,CAAC;AAC7C,eAAe,gBAAgB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAkB,MAAM,eAAe,CAAC;AAO7F;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,gBAAqB,GAAG,qBAAqB,
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/middleware/rate-limit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAkB,MAAM,eAAe,CAAC;AAO7F;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,gBAAqB,GAAG,qBAAqB,CAuIvF;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,0BAAoB,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @arcis/node/middleware/signup-protection
|
|
3
|
+
*
|
|
4
|
+
* Composite signup-form protection: one middleware that combines email
|
|
5
|
+
* validation (syntax + disposable), bot detection, and a dedicated
|
|
6
|
+
* per-IP rate limit. Matches the Arcjet `protectSignup` convenience
|
|
7
|
+
* primitive but stays fully local — no cloud lookups.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* app.post('/signup', signupProtection(), handler);
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* app.post('/signup', signupProtection({
|
|
14
|
+
* emailField: 'email',
|
|
15
|
+
* rateLimit: { max: 5, windowMs: 60_000 },
|
|
16
|
+
* blockDisposable: true,
|
|
17
|
+
* }), handler);
|
|
18
|
+
*/
|
|
19
|
+
import type { Request, RequestHandler } from 'express';
|
|
20
|
+
import { type BotCategory } from './bot-detection';
|
|
21
|
+
export type SignupBlockReason = 'missing_email' | 'invalid_email' | 'disposable_email' | 'bot' | 'rate_limited';
|
|
22
|
+
export interface SignupCheckResult {
|
|
23
|
+
allowed: boolean;
|
|
24
|
+
reason: SignupBlockReason | 'ok';
|
|
25
|
+
details?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export interface SignupProtectionOptions {
|
|
28
|
+
/** Request body field holding the email address. Default: 'email' */
|
|
29
|
+
emailField?: string;
|
|
30
|
+
/** Run email validation. Default: true */
|
|
31
|
+
checkEmail?: boolean;
|
|
32
|
+
/** Reject disposable email domains. Default: true */
|
|
33
|
+
blockDisposable?: boolean;
|
|
34
|
+
/** Run bot detection. Default: true */
|
|
35
|
+
checkBot?: boolean;
|
|
36
|
+
/** Bot categories allowed through (e.g. test harnesses). Default: [] — all bots blocked */
|
|
37
|
+
allowedBotCategories?: BotCategory[];
|
|
38
|
+
/** Per-IP rate limit on signup endpoint. Set to `false` to disable. Default: 5 requests / 60s */
|
|
39
|
+
rateLimit?: {
|
|
40
|
+
max?: number;
|
|
41
|
+
windowMs?: number;
|
|
42
|
+
} | false;
|
|
43
|
+
/** Extra email domains to allow (bypasses disposable check) */
|
|
44
|
+
allowedEmailDomains?: string[];
|
|
45
|
+
/** Extra email domains to block */
|
|
46
|
+
blockedEmailDomains?: string[];
|
|
47
|
+
/** Called when a request is blocked — for telemetry/logging */
|
|
48
|
+
onBlocked?: (req: Request, result: SignupCheckResult) => void;
|
|
49
|
+
}
|
|
50
|
+
export interface SignupProtectionMiddleware extends RequestHandler {
|
|
51
|
+
/** Release the rate-limiter cleanup interval */
|
|
52
|
+
close: () => void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Pure signup check — no rate-limit mutation, no response writes.
|
|
56
|
+
* Useful for framework adapters or custom control flow.
|
|
57
|
+
*/
|
|
58
|
+
export declare function checkSignup(req: Request, options?: SignupProtectionOptions): SignupCheckResult;
|
|
59
|
+
/**
|
|
60
|
+
* Express middleware: applies bot + email + rate-limit checks to a signup
|
|
61
|
+
* endpoint. Responds 400/403/429 with a JSON body on block; otherwise
|
|
62
|
+
* calls `next()`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function signupProtection(options?: SignupProtectionOptions): SignupProtectionMiddleware;
|
|
65
|
+
//# sourceMappingURL=signup-protection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signup-protection.d.ts","sourceRoot":"","sources":["../../src/middleware/signup-protection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAG9D,MAAM,MAAM,iBAAiB,GACzB,eAAe,GACf,eAAe,GACf,kBAAkB,GAClB,KAAK,GACL,cAAc,CAAC;AAEnB,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,uBAAuB;IACtC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2FAA2F;IAC3F,oBAAoB,CAAC,EAAE,WAAW,EAAE,CAAC;IACrC,iGAAiG;IACjG,SAAS,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;IACxD,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,mCAAmC;IACnC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC/D;AAED,MAAM,WAAW,0BAA2B,SAAQ,cAAc;IAChE,gDAAgD;IAChD,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,OAAO,EACZ,OAAO,GAAE,uBAA4B,GACpC,iBAAiB,CAwCnB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,uBAA4B,GACpC,0BAA0B,CAiD5B"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @arcis/node/middleware/telemetry
|
|
3
|
+
* Bridges Arcis middleware decisions to a TelemetryClient.
|
|
4
|
+
* Pattern 3 (two-layer): the client owns transport; this layer owns Express plumbing.
|
|
5
|
+
*/
|
|
6
|
+
import type { RequestHandler } from 'express';
|
|
7
|
+
import type { TelemetryClient } from '../telemetry/client';
|
|
8
|
+
import type { TelemetryDecision, TelemetrySeverity } from '../telemetry/types';
|
|
9
|
+
/** Marker that inner middleware writes to and the emitter reads from. */
|
|
10
|
+
export interface ArcisTelemetryMarker {
|
|
11
|
+
vector?: string;
|
|
12
|
+
rule?: string;
|
|
13
|
+
severity?: TelemetrySeverity;
|
|
14
|
+
matchedPattern?: string;
|
|
15
|
+
reason?: string;
|
|
16
|
+
/** Pre-decided decision. If absent, the emitter infers from response status. */
|
|
17
|
+
decision?: TelemetryDecision;
|
|
18
|
+
}
|
|
19
|
+
declare module 'express-serve-static-core' {
|
|
20
|
+
interface Request {
|
|
21
|
+
/** Per-request marker populated by Arcis middlewares for telemetry attribution. */
|
|
22
|
+
__arcis?: ArcisTelemetryMarker;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Express middleware that records a telemetry event for every request.
|
|
27
|
+
* Captures latency from entry, hooks `res.on('finish')`, and infers the
|
|
28
|
+
* final decision from response status + any `req.__arcis` marker.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createTelemetryEmitter(client: TelemetryClient): RequestHandler;
|
|
31
|
+
/**
|
|
32
|
+
* Wraps the sanitizer middleware so SecurityThreatError → req.__arcis marker.
|
|
33
|
+
* The emitter on `finish` will then have vector/rule/severity attribution.
|
|
34
|
+
*/
|
|
35
|
+
export declare function tapSanitizerThreats(handler: RequestHandler): RequestHandler;
|
|
36
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/middleware/telemetry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAW,cAAc,EAAE,MAAM,SAAS,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAkB,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG/F,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,OAAO,QAAQ,2BAA2B,CAAC;IACzC,UAAU,OAAO;QACf,mFAAmF;QACnF,OAAO,CAAC,EAAE,oBAAoB,CAAC;KAChC;CACF;AAcD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,CAe9E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CAiB3E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../src/sanitizers/encode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiBxD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../src/sanitizers/encode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiBxD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAwBjD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBlD"}
|
|
@@ -15,5 +15,6 @@ export { sanitizeJsonpCallback, detectJsonpInjection } from './jsonp';
|
|
|
15
15
|
export { sanitizeHeaderValue, sanitizeHeaders, detectHeaderInjection } from './headers';
|
|
16
16
|
export { scanPii, detectPii, redactPii, scanObjectPii, redactObjectPii } from './pii';
|
|
17
17
|
export { encodeForHtml, encodeForAttribute, encodeForJs, encodeForUrl, encodeForCss } from './encode';
|
|
18
|
+
export { sanitizeLdapFilter, sanitizeLdapDn, detectLdapInjection } from './ldap';
|
|
18
19
|
export { encodeHtmlEntities, isPlainObject } from './utils';
|
|
19
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sanitizers/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAGpE,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAG3F,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGnG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGlD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG/C,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAGtE,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAGxF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAGtF,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGtG,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sanitizers/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAGpE,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAG3F,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAGnG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGlD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG/C,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAGtE,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAGxF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAGtF,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGtG,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAGjF,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/sanitizers/index.js
CHANGED
|
@@ -27,13 +27,24 @@ var XSS_PATTERNS = [
|
|
|
27
27
|
/** URL-encoded script tags */
|
|
28
28
|
/%3Cscript/gi,
|
|
29
29
|
/** SVG with onload */
|
|
30
|
-
/<svg[^>]*onload/gi
|
|
30
|
+
/<svg[^>]*onload/gi,
|
|
31
|
+
/** form tags — phishing/credential harvesting via action= redirection */
|
|
32
|
+
/<form[\s>]/gi,
|
|
33
|
+
/** meta tags — http-equiv refresh redirects or CSP bypass */
|
|
34
|
+
/<meta[\s>]/gi,
|
|
35
|
+
/** base href hijacking — redirects all relative URLs to attacker domain */
|
|
36
|
+
/<base[\s>]/gi,
|
|
37
|
+
/** link tag injection — stylesheet or preload CSRF attacks */
|
|
38
|
+
/<link[\s>]/gi
|
|
31
39
|
];
|
|
32
40
|
var XSS_REMOVE_PATTERNS = [
|
|
33
41
|
/** Full script blocks (content + tags) */
|
|
34
42
|
/<script[^>]*>[\s\S]*?<\/script>/gi,
|
|
35
43
|
/** Standalone/unclosed script tags */
|
|
36
44
|
/<script[^>]*>/gi,
|
|
45
|
+
/** style — CSS expression() and behavior: attacks (IE-era but still relevant) */
|
|
46
|
+
/<style[^>]*>[\s\S]*?<\/style>/gi,
|
|
47
|
+
/<style[^>]*/gi,
|
|
37
48
|
/** iframe — full block and partial/unclosed */
|
|
38
49
|
/<iframe[^>]*>[\s\S]*?<\/iframe>/gi,
|
|
39
50
|
/<iframe[^>]*/gi,
|
|
@@ -54,7 +65,15 @@ var XSS_REMOVE_PATTERNS = [
|
|
|
54
65
|
/javascript\s*:/gi,
|
|
55
66
|
/vbscript\s*:/gi,
|
|
56
67
|
/** data: URIs with HTML/script content */
|
|
57
|
-
/data\s*:\s*text\/html[^>\s]*/gi
|
|
68
|
+
/data\s*:\s*text\/html[^>\s]*/gi,
|
|
69
|
+
/** form tag injection — phishing via action= redirection */
|
|
70
|
+
/<form[\s>][^>]*/gi,
|
|
71
|
+
/** meta tag injection — http-equiv refresh or CSP bypass */
|
|
72
|
+
/<meta[\s>][^>]*/gi,
|
|
73
|
+
/** base href hijacking */
|
|
74
|
+
/<base[\s>][^>]*/gi,
|
|
75
|
+
/** link tag injection — stylesheet or preload attacks */
|
|
76
|
+
/<link[\s>][^>]*/gi
|
|
58
77
|
];
|
|
59
78
|
var SQL_PATTERNS = [
|
|
60
79
|
/** SQL keywords */
|
|
@@ -118,8 +137,8 @@ var COMMAND_PATTERNS = [
|
|
|
118
137
|
/[;&|`]/g,
|
|
119
138
|
/** Command substitution: $( ... ) — matched as a pair to reduce false positives */
|
|
120
139
|
/\$\(/g,
|
|
121
|
-
/** URL-encoded
|
|
122
|
-
/%0[
|
|
140
|
+
/** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
|
|
141
|
+
/%0[0-9a-f]/gi
|
|
123
142
|
];
|
|
124
143
|
var DANGEROUS_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
125
144
|
"__proto__",
|
|
@@ -323,26 +342,31 @@ function sanitizePath(input, collectThreats = false) {
|
|
|
323
342
|
const threats = [];
|
|
324
343
|
let value = input;
|
|
325
344
|
let wasSanitized = false;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
345
|
+
value = value.normalize("NFKC");
|
|
346
|
+
let prev;
|
|
347
|
+
do {
|
|
348
|
+
prev = value;
|
|
349
|
+
for (const pattern of PATH_PATTERNS) {
|
|
329
350
|
pattern.lastIndex = 0;
|
|
330
|
-
if (
|
|
331
|
-
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
351
|
+
if (pattern.test(value)) {
|
|
352
|
+
pattern.lastIndex = 0;
|
|
353
|
+
if (collectThreats) {
|
|
354
|
+
const matches = value.match(pattern);
|
|
355
|
+
if (matches) {
|
|
356
|
+
for (const match of matches) {
|
|
357
|
+
threats.push({
|
|
358
|
+
type: "path_traversal",
|
|
359
|
+
pattern: pattern.source,
|
|
360
|
+
original: match
|
|
361
|
+
});
|
|
362
|
+
}
|
|
339
363
|
}
|
|
340
364
|
}
|
|
365
|
+
value = value.replace(pattern, "");
|
|
366
|
+
wasSanitized = true;
|
|
341
367
|
}
|
|
342
|
-
value = value.replace(pattern, "");
|
|
343
|
-
wasSanitized = true;
|
|
344
368
|
}
|
|
345
|
-
}
|
|
369
|
+
} while (value !== prev);
|
|
346
370
|
if (collectThreats) {
|
|
347
371
|
return { value, wasSanitized, threats };
|
|
348
372
|
}
|
|
@@ -350,9 +374,10 @@ function sanitizePath(input, collectThreats = false) {
|
|
|
350
374
|
}
|
|
351
375
|
function detectPathTraversal(input) {
|
|
352
376
|
if (typeof input !== "string") return false;
|
|
377
|
+
const normalized = input.normalize("NFKC");
|
|
353
378
|
for (const pattern of PATH_PATTERNS) {
|
|
354
379
|
pattern.lastIndex = 0;
|
|
355
|
-
if (pattern.test(
|
|
380
|
+
if (pattern.test(normalized)) {
|
|
356
381
|
return true;
|
|
357
382
|
}
|
|
358
383
|
}
|
|
@@ -410,7 +435,7 @@ function sanitizeString(value, options = {}) {
|
|
|
410
435
|
if (value.length > maxSize) {
|
|
411
436
|
throw new InputTooLargeError(maxSize, value.length);
|
|
412
437
|
}
|
|
413
|
-
const reject = options.mode
|
|
438
|
+
const reject = options.mode === "reject";
|
|
414
439
|
let result = value;
|
|
415
440
|
if (options.sql !== false) {
|
|
416
441
|
if (reject) {
|
|
@@ -565,10 +590,24 @@ var SSTI_DETECT_PATTERNS = [
|
|
|
565
590
|
/\{\{\s*(?:self|request|lipsum|cycler|joiner|namespace|range)\b/gi
|
|
566
591
|
];
|
|
567
592
|
var SSTI_REMOVE_PATTERNS = [
|
|
593
|
+
/** Jinja2 / Twig: {{ ... }} — always strip (not valid in any JS context) */
|
|
568
594
|
/\{\{.*?\}\}/g,
|
|
569
|
-
|
|
595
|
+
/**
|
|
596
|
+
* Freemarker / Spring EL: ${...} — strip when expression contains operators,
|
|
597
|
+
* method calls, or Python dunder patterns (sandbox escape).
|
|
598
|
+
* Bare ${name} and ${user.name} are left intact (JS template literal syntax).
|
|
599
|
+
*/
|
|
600
|
+
/\$\{[^}]*__\w+__[^}]*\}/g,
|
|
601
|
+
/\$\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
602
|
+
/** ERB / EJS: <%= ... %> */
|
|
570
603
|
/<%[=\-]?.*?%>/gs,
|
|
571
|
-
|
|
604
|
+
/**
|
|
605
|
+
* Pug / Jade: #{...} — same narrowing as ${ above, plus dunder detection.
|
|
606
|
+
* #{name} output expressions are left intact.
|
|
607
|
+
*/
|
|
608
|
+
/#\{[^}]*__\w+__[^}]*\}/g,
|
|
609
|
+
/#\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
610
|
+
/** Python dunder sandbox escape — always strip */
|
|
572
611
|
/__(?:class|mro|subclasses|globals|builtins|import)__/gi
|
|
573
612
|
];
|
|
574
613
|
function sanitizeSsti(input, collectThreats = false) {
|
|
@@ -615,6 +654,8 @@ function detectSsti(input) {
|
|
|
615
654
|
}
|
|
616
655
|
|
|
617
656
|
// src/sanitizers/xxe.ts
|
|
657
|
+
var MAX_XXE_INPUT_BYTES = 1e6;
|
|
658
|
+
var MAX_ENTITY_REFERENCES = 64;
|
|
618
659
|
var XXE_DETECT_PATTERNS = [
|
|
619
660
|
/** DOCTYPE declaration */
|
|
620
661
|
/<!DOCTYPE\b/gi,
|
|
@@ -644,6 +685,19 @@ function sanitizeXxe(input, collectThreats = false) {
|
|
|
644
685
|
const threats = [];
|
|
645
686
|
let value = input;
|
|
646
687
|
let wasSanitized = false;
|
|
688
|
+
if (value.length > MAX_XXE_INPUT_BYTES) {
|
|
689
|
+
if (collectThreats) {
|
|
690
|
+
threats.push({ type: "xxe", pattern: "oversize_input", original: `length=${value.length}` });
|
|
691
|
+
}
|
|
692
|
+
return collectThreats ? { value: "", wasSanitized: true, threats } : "";
|
|
693
|
+
}
|
|
694
|
+
const entityRefs = value.match(/&\w+;/g);
|
|
695
|
+
if (entityRefs && entityRefs.length > MAX_ENTITY_REFERENCES) {
|
|
696
|
+
if (collectThreats) {
|
|
697
|
+
threats.push({ type: "xxe", pattern: "entity_expansion", original: `count=${entityRefs.length}` });
|
|
698
|
+
}
|
|
699
|
+
return collectThreats ? { value: "", wasSanitized: true, threats } : "";
|
|
700
|
+
}
|
|
647
701
|
for (const pattern of XXE_REMOVE_PATTERNS) {
|
|
648
702
|
pattern.lastIndex = 0;
|
|
649
703
|
if (pattern.test(value)) {
|
|
@@ -681,12 +735,10 @@ function detectXxe(input) {
|
|
|
681
735
|
}
|
|
682
736
|
|
|
683
737
|
// src/sanitizers/jsonp.ts
|
|
684
|
-
var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.
|
|
738
|
+
var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/;
|
|
685
739
|
var DANGEROUS_CALLBACK_PATTERNS = [
|
|
686
|
-
|
|
740
|
+
/\.\./
|
|
687
741
|
// prototype chain traversal
|
|
688
|
-
/\[\s*\]/
|
|
689
|
-
// empty bracket access
|
|
690
742
|
];
|
|
691
743
|
function sanitizeJsonpCallback(callback, maxLength = 128) {
|
|
692
744
|
if (typeof callback !== "string" || callback.length === 0) {
|
|
@@ -771,7 +823,7 @@ function detectHeaderInjection(input) {
|
|
|
771
823
|
|
|
772
824
|
// src/sanitizers/pii.ts
|
|
773
825
|
var EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z]{2,})+/g;
|
|
774
|
-
var PHONE_RE = /(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g;
|
|
826
|
+
var PHONE_RE = /(?<!\d)(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?!\d)/g;
|
|
775
827
|
var CREDIT_CARD_RE = /\b(?:\d[ -]*?){13,19}\b/g;
|
|
776
828
|
var SSN_RE = /\b\d{3}[-\s]\d{2}[-\s]\d{4}\b/g;
|
|
777
829
|
var IPV4_RE = /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g;
|
|
@@ -932,16 +984,18 @@ function encodeForAttribute(value) {
|
|
|
932
984
|
function encodeForJs(value) {
|
|
933
985
|
if (!value) return "";
|
|
934
986
|
let result = "";
|
|
935
|
-
for (
|
|
936
|
-
const
|
|
937
|
-
if (
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
result +=
|
|
941
|
-
} else if (
|
|
942
|
-
result += `\\x${
|
|
987
|
+
for (const char of value) {
|
|
988
|
+
const cp = char.codePointAt(0);
|
|
989
|
+
if (cp >= 48 && cp <= 57 || // 0-9
|
|
990
|
+
cp >= 65 && cp <= 90 || // A-Z
|
|
991
|
+
cp >= 97 && cp <= 122) {
|
|
992
|
+
result += char;
|
|
993
|
+
} else if (cp < 256) {
|
|
994
|
+
result += `\\x${cp.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
995
|
+
} else if (cp <= 65535) {
|
|
996
|
+
result += `\\u${cp.toString(16).toUpperCase().padStart(4, "0")}`;
|
|
943
997
|
} else {
|
|
944
|
-
result += `\\u${
|
|
998
|
+
result += `\\u{${cp.toString(16).toUpperCase()}}`;
|
|
945
999
|
}
|
|
946
1000
|
}
|
|
947
1001
|
return result;
|
|
@@ -968,10 +1022,30 @@ function encodeForCss(value) {
|
|
|
968
1022
|
return result;
|
|
969
1023
|
}
|
|
970
1024
|
|
|
1025
|
+
// src/sanitizers/ldap.ts
|
|
1026
|
+
var LDAP_FILTER_CHARS = /[*()\\\x00]/g;
|
|
1027
|
+
var LDAP_DN_CHARS = /[,+<>;"=\/\\\x00*()\x00]/g;
|
|
1028
|
+
var LDAP_DETECT_PATTERN = /[*()\\\x00]/;
|
|
1029
|
+
var LDAP_INJECTION_PATTERN = /\)\s*\(|\*\s*\)\s*\(/;
|
|
1030
|
+
var escapeChar = (char) => "\\" + char.charCodeAt(0).toString(16).padStart(2, "0");
|
|
1031
|
+
function sanitizeLdapFilter(input) {
|
|
1032
|
+
if (typeof input !== "string") return String(input);
|
|
1033
|
+
return input.replace(LDAP_FILTER_CHARS, escapeChar);
|
|
1034
|
+
}
|
|
1035
|
+
function sanitizeLdapDn(input) {
|
|
1036
|
+
if (typeof input !== "string") return String(input);
|
|
1037
|
+
return input.replace(LDAP_DN_CHARS, escapeChar);
|
|
1038
|
+
}
|
|
1039
|
+
function detectLdapInjection(input) {
|
|
1040
|
+
if (typeof input !== "string") return false;
|
|
1041
|
+
return LDAP_DETECT_PATTERN.test(input) || LDAP_INJECTION_PATTERN.test(input);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
971
1044
|
exports.createSanitizer = createSanitizer;
|
|
972
1045
|
exports.detectCommandInjection = detectCommandInjection;
|
|
973
1046
|
exports.detectHeaderInjection = detectHeaderInjection;
|
|
974
1047
|
exports.detectJsonpInjection = detectJsonpInjection;
|
|
1048
|
+
exports.detectLdapInjection = detectLdapInjection;
|
|
975
1049
|
exports.detectNoSqlInjection = detectNoSqlInjection;
|
|
976
1050
|
exports.detectPathTraversal = detectPathTraversal;
|
|
977
1051
|
exports.detectPii = detectPii;
|
|
@@ -997,6 +1071,8 @@ exports.sanitizeCommand = sanitizeCommand;
|
|
|
997
1071
|
exports.sanitizeHeaderValue = sanitizeHeaderValue;
|
|
998
1072
|
exports.sanitizeHeaders = sanitizeHeaders;
|
|
999
1073
|
exports.sanitizeJsonpCallback = sanitizeJsonpCallback;
|
|
1074
|
+
exports.sanitizeLdapDn = sanitizeLdapDn;
|
|
1075
|
+
exports.sanitizeLdapFilter = sanitizeLdapFilter;
|
|
1000
1076
|
exports.sanitizeObject = sanitizeObject;
|
|
1001
1077
|
exports.sanitizePath = sanitizePath;
|
|
1002
1078
|
exports.sanitizeSql = sanitizeSql;
|