@arcis/node 1.4.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +36 -6
- package/dist/astro/index.js +6141 -0
- package/dist/astro/index.js.map +1 -0
- package/dist/astro/index.mjs +6136 -0
- package/dist/astro/index.mjs.map +1 -0
- package/dist/bun/index.js +6195 -0
- package/dist/bun/index.js.map +1 -0
- package/dist/bun/index.mjs +6189 -0
- package/dist/bun/index.mjs.map +1 -0
- package/dist/core/constants.d.ts +3 -2
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/index.js +4 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +4 -3
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/types.d.ts +32 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/fastify/index.js +6160 -0
- package/dist/fastify/index.js.map +1 -0
- package/dist/fastify/index.mjs +6155 -0
- package/dist/fastify/index.mjs.map +1 -0
- package/dist/guards.d.ts +156 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/hono/index.js +6159 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/hono/index.mjs +6154 -0
- package/dist/hono/index.mjs.map +1 -0
- package/dist/index.d.ts +23 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7126 -178
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7088 -179
- package/dist/index.mjs.map +1 -1
- package/dist/koa/index.js +6158 -0
- package/dist/koa/index.js.map +1 -0
- package/dist/koa/index.mjs +6153 -0
- package/dist/koa/index.mjs.map +1 -0
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs.map +1 -1
- package/dist/logging/redactor.d.ts.map +1 -1
- package/dist/middleware/astro.d.ts +64 -0
- package/dist/middleware/astro.d.ts.map +1 -0
- package/dist/middleware/bot-detection.d.ts.map +1 -1
- package/dist/middleware/bun.d.ts +75 -0
- package/dist/middleware/bun.d.ts.map +1 -0
- package/dist/middleware/csrf.d.ts.map +1 -1
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/fastify.d.ts +89 -0
- package/dist/middleware/fastify.d.ts.map +1 -0
- package/dist/middleware/graphql.d.ts +35 -0
- package/dist/middleware/graphql.d.ts.map +1 -0
- package/dist/middleware/hono.d.ts +63 -0
- package/dist/middleware/hono.d.ts.map +1 -0
- package/dist/middleware/index.d.ts +12 -0
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +6469 -119
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +6459 -120
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/middleware/koa.d.ts +84 -0
- package/dist/middleware/koa.d.ts.map +1 -0
- package/dist/middleware/main.d.ts +0 -30
- package/dist/middleware/main.d.ts.map +1 -1
- package/dist/middleware/mass-assign.d.ts +81 -0
- package/dist/middleware/mass-assign.d.ts.map +1 -0
- package/dist/middleware/method-allowlist.d.ts +66 -0
- package/dist/middleware/method-allowlist.d.ts.map +1 -0
- package/dist/middleware/nestjs.d.ts +62 -0
- package/dist/middleware/nestjs.d.ts.map +1 -0
- package/dist/middleware/nextjs.d.ts +102 -0
- package/dist/middleware/nextjs.d.ts.map +1 -0
- package/dist/middleware/nuxt.d.ts +61 -0
- package/dist/middleware/nuxt.d.ts.map +1 -0
- package/dist/middleware/overload.d.ts +92 -0
- package/dist/middleware/overload.d.ts.map +1 -0
- package/dist/middleware/protect.d.ts +91 -0
- package/dist/middleware/protect.d.ts.map +1 -0
- package/dist/middleware/rate-limit-sliding.d.ts.map +1 -1
- package/dist/middleware/rate-limit-token.d.ts.map +1 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/response-splitting.d.ts +83 -0
- package/dist/middleware/response-splitting.d.ts.map +1 -0
- package/dist/middleware/sveltekit.d.ts +68 -0
- package/dist/middleware/sveltekit.d.ts.map +1 -0
- package/dist/middleware/token-budget.d.ts +75 -0
- package/dist/middleware/token-budget.d.ts.map +1 -0
- package/dist/nestjs/index.js +1724 -0
- package/dist/nestjs/index.js.map +1 -0
- package/dist/nestjs/index.mjs +1717 -0
- package/dist/nestjs/index.mjs.map +1 -0
- package/dist/nextjs/index.js +6184 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/nextjs/index.mjs +6178 -0
- package/dist/nextjs/index.mjs.map +1 -0
- package/dist/nuxt/index.js +6141 -0
- package/dist/nuxt/index.js.map +1 -0
- package/dist/nuxt/index.mjs +6136 -0
- package/dist/nuxt/index.mjs.map +1 -0
- package/dist/sanitizers/encode.d.ts.map +1 -1
- package/dist/sanitizers/graphql.d.ts +72 -0
- package/dist/sanitizers/graphql.d.ts.map +1 -0
- package/dist/sanitizers/headers.d.ts +18 -0
- package/dist/sanitizers/headers.d.ts.map +1 -1
- package/dist/sanitizers/index.d.ts +4 -1
- package/dist/sanitizers/index.d.ts.map +1 -1
- package/dist/sanitizers/index.js +140 -66
- package/dist/sanitizers/index.js.map +1 -1
- package/dist/sanitizers/index.mjs +135 -67
- package/dist/sanitizers/index.mjs.map +1 -1
- package/dist/sanitizers/prompt-injection.d.ts +62 -0
- package/dist/sanitizers/prompt-injection.d.ts.map +1 -0
- package/dist/sanitizers/sanitize.d.ts +1 -1
- package/dist/sanitizers/sanitize.d.ts.map +1 -1
- package/dist/sanitizers/xpath.d.ts +37 -0
- package/dist/sanitizers/xpath.d.ts.map +1 -0
- package/dist/stores/index.js +4 -4
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/index.mjs +4 -4
- package/dist/stores/index.mjs.map +1 -1
- package/dist/stores/redis.d.ts +7 -1
- package/dist/stores/redis.d.ts.map +1 -1
- package/dist/sveltekit/index.js +6142 -0
- package/dist/sveltekit/index.js.map +1 -0
- package/dist/sveltekit/index.mjs +6137 -0
- package/dist/sveltekit/index.mjs.map +1 -0
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +137 -12
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/index.mjs +116 -13
- package/dist/validation/index.mjs.map +1 -1
- package/dist/validation/redirect.d.ts.map +1 -1
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/url-async.d.ts +137 -0
- package/dist/validation/url-async.d.ts.map +1 -0
- package/package.json +52 -7
- package/scripts/postinstall.cjs +26 -0
- package/dist/cli/arcis.d.ts +0 -23
- package/dist/cli/arcis.d.ts.map +0 -1
- package/dist/cli/arcis.js +0 -312
- package/dist/cli/arcis.js.map +0 -1
- package/dist/cli/arcis.mjs +0 -309
- package/dist/cli/arcis.mjs.map +0 -1
|
@@ -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,CAyBjD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBlD"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @arcis/node/sanitizers/graphql
|
|
3
|
+
* GraphQL injection prevention (sdk-vectors.md tier 1 #21).
|
|
4
|
+
*
|
|
5
|
+
* Two threats covered:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Depth-bomb DoS** — nested-query payloads like
|
|
8
|
+
* `query { x { x { x { ... } } } }` to ridiculous depth that explode
|
|
9
|
+
* resolver work (each `{` typically maps to a database round-trip).
|
|
10
|
+
* Even a 50-deep query against a real schema can hammer the
|
|
11
|
+
* backend; 1000-deep crashes the resolver entirely.
|
|
12
|
+
*
|
|
13
|
+
* 2. **Introspection abuse** — `__schema` / `__type` / `__typename`
|
|
14
|
+
* queries that let an attacker enumerate the entire schema, then
|
|
15
|
+
* use that map to find sensitive fields, deprecated mutations,
|
|
16
|
+
* or unprotected admin paths. Production GraphQL endpoints should
|
|
17
|
+
* disable introspection by default.
|
|
18
|
+
*
|
|
19
|
+
* v1 is regex-based: count `{` / `}` for nesting depth (no escape
|
|
20
|
+
* handling — strings inside the query that contain `{` will
|
|
21
|
+
* over-count). False positives are an acceptable tradeoff for v1
|
|
22
|
+
* because (a) the depth threshold is well above legitimate query
|
|
23
|
+
* shapes, (b) a real GraphQL parser pulls in `graphql` as a runtime
|
|
24
|
+
* dep — significant for a sanitizer that ships in every Arcis
|
|
25
|
+
* install. Customers running queries near the threshold can either
|
|
26
|
+
* raise `maxDepth` or bring their own AST pre-pass.
|
|
27
|
+
*
|
|
28
|
+
* NOT included in v1:
|
|
29
|
+
* - Field-count limit (some servers have this; orthogonal to depth)
|
|
30
|
+
* - Alias-bomb detection (`q { f1: foo, f2: foo, ...}` — easier as
|
|
31
|
+
* a length-check than a parse)
|
|
32
|
+
* - Variable rebinding attacks
|
|
33
|
+
*
|
|
34
|
+
* Each is a follow-up if customers ask. Documented inline.
|
|
35
|
+
*/
|
|
36
|
+
export interface GraphqlGuardOptions {
|
|
37
|
+
/** Maximum allowed nesting depth. Default: 10. Most legit queries are <8. */
|
|
38
|
+
maxDepth?: number;
|
|
39
|
+
/** Maximum query string length in characters. Default: 10000. */
|
|
40
|
+
maxLength?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Block introspection queries (`__schema`, `__type`). Default: true.
|
|
43
|
+
* Set `false` in development if you rely on GraphiQL / Apollo
|
|
44
|
+
* Studio. Production should leave this on.
|
|
45
|
+
*/
|
|
46
|
+
blockIntrospection?: boolean;
|
|
47
|
+
}
|
|
48
|
+
export type GraphqlViolation = 'depth' | 'length' | 'introspection';
|
|
49
|
+
export interface GraphqlGuardResult {
|
|
50
|
+
/** True if the query violated any configured limit. */
|
|
51
|
+
blocked: boolean;
|
|
52
|
+
/** Which limit fired first (depth → introspection → length precedence). */
|
|
53
|
+
reason?: GraphqlViolation;
|
|
54
|
+
/** Observed nesting depth. Always returned, even on clean queries. */
|
|
55
|
+
depth: number;
|
|
56
|
+
/** Observed length. Always returned. */
|
|
57
|
+
length: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Inspect a GraphQL query against the configured limits. Returns a
|
|
61
|
+
* structured result; the middleware below uses this directly. Pure
|
|
62
|
+
* function — no I/O, no res handle.
|
|
63
|
+
*/
|
|
64
|
+
export declare function inspectGraphqlQuery(query: string, options?: GraphqlGuardOptions): GraphqlGuardResult;
|
|
65
|
+
/**
|
|
66
|
+
* Detect-only API matching the rest of the sanitizer module surface
|
|
67
|
+
* (`detectXss` / `detectSql` / `detectXxe` / etc.). Returns a boolean
|
|
68
|
+
* for callers that just want a yes/no — use `inspectGraphqlQuery` if
|
|
69
|
+
* you need the structured reason.
|
|
70
|
+
*/
|
|
71
|
+
export declare function detectGraphqlAbuse(query: string, options?: GraphqlGuardOptions): boolean;
|
|
72
|
+
//# sourceMappingURL=graphql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphql.d.ts","sourceRoot":"","sources":["../../src/sanitizers/graphql.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,WAAW,mBAAmB;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,GAAG,eAAe,CAAC;AAEpE,MAAM,WAAW,kBAAkB;IACjC,uDAAuD;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AA2CD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAwBpB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAGT"}
|
|
@@ -43,4 +43,22 @@ export declare function sanitizeHeaders(headers: Record<string, string>): Record
|
|
|
43
43
|
* @returns True if header injection patterns detected
|
|
44
44
|
*/
|
|
45
45
|
export declare function detectHeaderInjection(input: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Email-header injection prevention. Same byte-level threat as HTTP
|
|
48
|
+
* header injection — `\r\n` in a user-controlled email field
|
|
49
|
+
* (`To`, `From`, `Subject`, etc.) lets an attacker inject extra headers
|
|
50
|
+
* (most commonly Bcc) and pivot a contact form into a spam relay.
|
|
51
|
+
*
|
|
52
|
+
* Aliased to the HTTP-header sanitizers because the wire-level fix is
|
|
53
|
+
* identical: strip CRLF + null bytes from the value before
|
|
54
|
+
* concatenating into the header. Use these in form-to-email handlers:
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* const subject = sanitizeEmailHeader(req.body.subject);
|
|
58
|
+
* const to = sanitizeEmailHeader(req.body.to);
|
|
59
|
+
* if (detectEmailHeaderInjection(req.body.to)) reject(...);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare const sanitizeEmailHeader: typeof sanitizeHeaderValue;
|
|
63
|
+
export declare const detectEmailHeaderInjection: typeof detectHeaderInjection;
|
|
46
64
|
//# sourceMappingURL=headers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/sanitizers/headers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,eAAe,CAAC;AAUhE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;AACnF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,GAAG,cAAc,CAAC;AAuCzF;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAcvF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK5D"}
|
|
1
|
+
{"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/sanitizers/headers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,eAAe,CAAC;AAUhE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;AACnF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,IAAI,GAAG,cAAc,CAAC;AAuCzF;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAcvF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK5D;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,4BAAsB,CAAC;AACvD,eAAO,MAAM,0BAA0B,8BAAwB,CAAC"}
|
|
@@ -13,9 +13,12 @@ export { isDangerousProtoKey, detectPrototypePollution, getDangerousProtoKeys }
|
|
|
13
13
|
export { sanitizeSsti, detectSsti } from './ssti';
|
|
14
14
|
export { sanitizeXxe, detectXxe } from './xxe';
|
|
15
15
|
export { sanitizeJsonpCallback, detectJsonpInjection } from './jsonp';
|
|
16
|
-
export { sanitizeHeaderValue, sanitizeHeaders, detectHeaderInjection } from './headers';
|
|
16
|
+
export { sanitizeHeaderValue, sanitizeHeaders, detectHeaderInjection, sanitizeEmailHeader, detectEmailHeaderInjection, } from './headers';
|
|
17
17
|
export { scanPii, detectPii, redactPii, scanObjectPii, redactObjectPii } from './pii';
|
|
18
18
|
export { encodeForHtml, encodeForAttribute, encodeForJs, encodeForUrl, encodeForCss } from './encode';
|
|
19
19
|
export { sanitizeLdapFilter, sanitizeLdapDn, detectLdapInjection } from './ldap';
|
|
20
|
+
export { sanitizeXpath, detectXpathInjection } from './xpath';
|
|
21
|
+
export { inspectGraphqlQuery, detectGraphqlAbuse, } from './graphql';
|
|
22
|
+
export type { GraphqlGuardOptions, GraphqlGuardResult, GraphqlViolation, } from './graphql';
|
|
20
23
|
export { encodeHtmlEntities, isPlainObject } from './utils';
|
|
21
24
|
//# 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,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1F,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,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,
|
|
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,WAAW,EAAE,MAAM,YAAY,CAAC;AAC1F,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,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,EACL,mBAAmB,EACnB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,WAAW,CAAC;AAGnB,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,aAAa,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAG9D,OAAO,EACL,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,YAAY,EACV,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/sanitizers/index.js
CHANGED
|
@@ -67,8 +67,8 @@ var XSS_REMOVE_PATTERNS = [
|
|
|
67
67
|
/** javascript: and vbscript: protocols (allow optional spaces before colon) */
|
|
68
68
|
/javascript\s*:/gi,
|
|
69
69
|
/vbscript\s*:/gi,
|
|
70
|
-
/** data: URIs with HTML
|
|
71
|
-
/data\s*:\s*text\/html[^>\s]*/gi,
|
|
70
|
+
/** data: URIs with HTML or SVG content (SVG can run JS via inline event handlers) */
|
|
71
|
+
/data\s*:\s*(?:text\/html|image\/svg)[^>\s]*/gi,
|
|
72
72
|
/** form tag injection — phishing via action= redirection */
|
|
73
73
|
/<form[\s>][^>]*/gi,
|
|
74
74
|
/** meta tag injection — http-equiv refresh or CSP bypass */
|
|
@@ -593,6 +593,89 @@ function detectXxe(input) {
|
|
|
593
593
|
return false;
|
|
594
594
|
}
|
|
595
595
|
|
|
596
|
+
// src/sanitizers/ldap.ts
|
|
597
|
+
var LDAP_FILTER_CHARS = /[*()\\\x00]/g;
|
|
598
|
+
var LDAP_DN_CHARS = /[,+<>;"=\/\\\x00*()\x00]/g;
|
|
599
|
+
var LDAP_DETECT_PATTERN = /[*()\\\x00]/;
|
|
600
|
+
var LDAP_INJECTION_PATTERN = /\)\s*\(|\*\s*\)\s*\(/;
|
|
601
|
+
var escapeChar = (char) => "\\" + char.charCodeAt(0).toString(16).padStart(2, "0");
|
|
602
|
+
function sanitizeLdapFilter(input) {
|
|
603
|
+
if (typeof input !== "string") return String(input);
|
|
604
|
+
return input.replace(LDAP_FILTER_CHARS, escapeChar);
|
|
605
|
+
}
|
|
606
|
+
function sanitizeLdapDn(input) {
|
|
607
|
+
if (typeof input !== "string") return String(input);
|
|
608
|
+
return input.replace(LDAP_DN_CHARS, escapeChar);
|
|
609
|
+
}
|
|
610
|
+
function detectLdapInjection(input) {
|
|
611
|
+
if (typeof input !== "string") return false;
|
|
612
|
+
return LDAP_DETECT_PATTERN.test(input) || LDAP_INJECTION_PATTERN.test(input);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/sanitizers/xpath.ts
|
|
616
|
+
var XPATH_INJECTION_CHARS = /['"|,()]/;
|
|
617
|
+
var XPATH_INJECTION_PATTERN = /('\s*(or|and)\s*'|"\s*(or|and)\s*"|\)\s*(or|and)\s*\(|\|\s*\/)/i;
|
|
618
|
+
function detectXpathInjection(input) {
|
|
619
|
+
if (typeof input !== "string" || input.length === 0) return false;
|
|
620
|
+
if (!XPATH_INJECTION_CHARS.test(input)) return false;
|
|
621
|
+
return XPATH_INJECTION_PATTERN.test(input);
|
|
622
|
+
}
|
|
623
|
+
function sanitizeXpath(input) {
|
|
624
|
+
if (typeof input !== "string") return String(input);
|
|
625
|
+
return input.replace(/['"|,]/g, "");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/sanitizers/headers.ts
|
|
629
|
+
var HEADER_INJECTION_PATTERN = /\r\n|\r|\n|\0/g;
|
|
630
|
+
function sanitizeHeaderValue(input, collectThreats = false) {
|
|
631
|
+
if (typeof input !== "string") {
|
|
632
|
+
return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
|
|
633
|
+
}
|
|
634
|
+
const threats = [];
|
|
635
|
+
let wasSanitized = false;
|
|
636
|
+
if (HEADER_INJECTION_PATTERN.test(input)) {
|
|
637
|
+
HEADER_INJECTION_PATTERN.lastIndex = 0;
|
|
638
|
+
wasSanitized = true;
|
|
639
|
+
if (collectThreats) {
|
|
640
|
+
const matches = input.match(HEADER_INJECTION_PATTERN);
|
|
641
|
+
if (matches) {
|
|
642
|
+
for (const match of matches) {
|
|
643
|
+
threats.push({
|
|
644
|
+
type: "header_injection",
|
|
645
|
+
pattern: HEADER_INJECTION_PATTERN.source,
|
|
646
|
+
original: match
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
HEADER_INJECTION_PATTERN.lastIndex = 0;
|
|
653
|
+
const value = input.replace(HEADER_INJECTION_PATTERN, "");
|
|
654
|
+
if (collectThreats) {
|
|
655
|
+
return { value, wasSanitized, threats };
|
|
656
|
+
}
|
|
657
|
+
return value;
|
|
658
|
+
}
|
|
659
|
+
function sanitizeHeaders(headers) {
|
|
660
|
+
if (!headers || typeof headers !== "object") {
|
|
661
|
+
return {};
|
|
662
|
+
}
|
|
663
|
+
const result = {};
|
|
664
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
665
|
+
const sanitizedKey = sanitizeHeaderValue(String(key));
|
|
666
|
+
const sanitizedValue = sanitizeHeaderValue(String(value));
|
|
667
|
+
result[sanitizedKey] = sanitizedValue;
|
|
668
|
+
}
|
|
669
|
+
return result;
|
|
670
|
+
}
|
|
671
|
+
function detectHeaderInjection(input) {
|
|
672
|
+
if (typeof input !== "string") return false;
|
|
673
|
+
HEADER_INJECTION_PATTERN.lastIndex = 0;
|
|
674
|
+
return HEADER_INJECTION_PATTERN.test(input);
|
|
675
|
+
}
|
|
676
|
+
var sanitizeEmailHeader = sanitizeHeaderValue;
|
|
677
|
+
var detectEmailHeaderInjection = detectHeaderInjection;
|
|
678
|
+
|
|
596
679
|
// src/sanitizers/sanitize.ts
|
|
597
680
|
function sanitizeString(value, options = {}) {
|
|
598
681
|
if (typeof value !== "string") return value;
|
|
@@ -705,6 +788,15 @@ function scanThreats(data, depth = 0) {
|
|
|
705
788
|
if (detectCommandInjection(data)) {
|
|
706
789
|
return { vector: "command", rule: "command/match", matchedPattern: sample };
|
|
707
790
|
}
|
|
791
|
+
if (detectLdapInjection(data)) {
|
|
792
|
+
return { vector: "ldap", rule: "ldap/match", matchedPattern: sample };
|
|
793
|
+
}
|
|
794
|
+
if (detectXpathInjection(data)) {
|
|
795
|
+
return { vector: "xpath", rule: "xpath/match", matchedPattern: sample };
|
|
796
|
+
}
|
|
797
|
+
if (detectHeaderInjection(data)) {
|
|
798
|
+
return { vector: "header", rule: "header/match", matchedPattern: sample };
|
|
799
|
+
}
|
|
708
800
|
return null;
|
|
709
801
|
}
|
|
710
802
|
function createSanitizer(options = {}) {
|
|
@@ -839,55 +931,6 @@ function detectJsonpInjection(callback) {
|
|
|
839
931
|
return false;
|
|
840
932
|
}
|
|
841
933
|
|
|
842
|
-
// src/sanitizers/headers.ts
|
|
843
|
-
var HEADER_INJECTION_PATTERN = /\r\n|\r|\n|\0/g;
|
|
844
|
-
function sanitizeHeaderValue(input, collectThreats = false) {
|
|
845
|
-
if (typeof input !== "string") {
|
|
846
|
-
return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
|
|
847
|
-
}
|
|
848
|
-
const threats = [];
|
|
849
|
-
let wasSanitized = false;
|
|
850
|
-
if (HEADER_INJECTION_PATTERN.test(input)) {
|
|
851
|
-
HEADER_INJECTION_PATTERN.lastIndex = 0;
|
|
852
|
-
wasSanitized = true;
|
|
853
|
-
if (collectThreats) {
|
|
854
|
-
const matches = input.match(HEADER_INJECTION_PATTERN);
|
|
855
|
-
if (matches) {
|
|
856
|
-
for (const match of matches) {
|
|
857
|
-
threats.push({
|
|
858
|
-
type: "header_injection",
|
|
859
|
-
pattern: HEADER_INJECTION_PATTERN.source,
|
|
860
|
-
original: match
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
HEADER_INJECTION_PATTERN.lastIndex = 0;
|
|
867
|
-
const value = input.replace(HEADER_INJECTION_PATTERN, "");
|
|
868
|
-
if (collectThreats) {
|
|
869
|
-
return { value, wasSanitized, threats };
|
|
870
|
-
}
|
|
871
|
-
return value;
|
|
872
|
-
}
|
|
873
|
-
function sanitizeHeaders(headers) {
|
|
874
|
-
if (!headers || typeof headers !== "object") {
|
|
875
|
-
return {};
|
|
876
|
-
}
|
|
877
|
-
const result = {};
|
|
878
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
879
|
-
const sanitizedKey = sanitizeHeaderValue(String(key));
|
|
880
|
-
const sanitizedValue = sanitizeHeaderValue(String(value));
|
|
881
|
-
result[sanitizedKey] = sanitizedValue;
|
|
882
|
-
}
|
|
883
|
-
return result;
|
|
884
|
-
}
|
|
885
|
-
function detectHeaderInjection(input) {
|
|
886
|
-
if (typeof input !== "string") return false;
|
|
887
|
-
HEADER_INJECTION_PATTERN.lastIndex = 0;
|
|
888
|
-
return HEADER_INJECTION_PATTERN.test(input);
|
|
889
|
-
}
|
|
890
|
-
|
|
891
934
|
// src/sanitizers/pii.ts
|
|
892
935
|
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;
|
|
893
936
|
var PHONE_RE = /(?<!\d)(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?!\d)/g;
|
|
@@ -1053,6 +1096,7 @@ function encodeForJs(value) {
|
|
|
1053
1096
|
let result = "";
|
|
1054
1097
|
for (const char of value) {
|
|
1055
1098
|
const cp = char.codePointAt(0);
|
|
1099
|
+
if (cp === void 0) continue;
|
|
1056
1100
|
if (cp >= 48 && cp <= 57 || // 0-9
|
|
1057
1101
|
cp >= 65 && cp <= 90 || // A-Z
|
|
1058
1102
|
cp >= 97 && cp <= 122) {
|
|
@@ -1089,27 +1133,53 @@ function encodeForCss(value) {
|
|
|
1089
1133
|
return result;
|
|
1090
1134
|
}
|
|
1091
1135
|
|
|
1092
|
-
// src/sanitizers/
|
|
1093
|
-
var
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1136
|
+
// src/sanitizers/graphql.ts
|
|
1137
|
+
var DEFAULTS = {
|
|
1138
|
+
maxDepth: 10,
|
|
1139
|
+
maxLength: 1e4,
|
|
1140
|
+
blockIntrospection: true
|
|
1141
|
+
};
|
|
1142
|
+
var INTROSPECTION_PATTERN = /\b__(schema|type|typeKind|directive)\b/;
|
|
1143
|
+
function computeDepth(query) {
|
|
1144
|
+
let depth = 0;
|
|
1145
|
+
let max = 0;
|
|
1146
|
+
for (let i = 0; i < query.length; i++) {
|
|
1147
|
+
const c = query.charCodeAt(i);
|
|
1148
|
+
if (c === 123) {
|
|
1149
|
+
depth++;
|
|
1150
|
+
if (depth > max) max = depth;
|
|
1151
|
+
} else if (c === 125) {
|
|
1152
|
+
if (depth > 0) depth--;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return max;
|
|
1101
1156
|
}
|
|
1102
|
-
function
|
|
1103
|
-
|
|
1104
|
-
|
|
1157
|
+
function inspectGraphqlQuery(query, options = {}) {
|
|
1158
|
+
const maxDepth = options.maxDepth ?? DEFAULTS.maxDepth;
|
|
1159
|
+
const maxLength = options.maxLength ?? DEFAULTS.maxLength;
|
|
1160
|
+
const blockIntrospection = options.blockIntrospection ?? DEFAULTS.blockIntrospection;
|
|
1161
|
+
const length = query.length;
|
|
1162
|
+
const depth = computeDepth(query);
|
|
1163
|
+
if (depth > maxDepth) {
|
|
1164
|
+
return { blocked: true, reason: "depth", depth, length };
|
|
1165
|
+
}
|
|
1166
|
+
if (blockIntrospection && INTROSPECTION_PATTERN.test(query)) {
|
|
1167
|
+
return { blocked: true, reason: "introspection", depth, length };
|
|
1168
|
+
}
|
|
1169
|
+
if (length > maxLength) {
|
|
1170
|
+
return { blocked: true, reason: "length", depth, length };
|
|
1171
|
+
}
|
|
1172
|
+
return { blocked: false, depth, length };
|
|
1105
1173
|
}
|
|
1106
|
-
function
|
|
1107
|
-
if (typeof
|
|
1108
|
-
return
|
|
1174
|
+
function detectGraphqlAbuse(query, options) {
|
|
1175
|
+
if (typeof query !== "string" || query.length === 0) return false;
|
|
1176
|
+
return inspectGraphqlQuery(query, options).blocked;
|
|
1109
1177
|
}
|
|
1110
1178
|
|
|
1111
1179
|
exports.createSanitizer = createSanitizer;
|
|
1112
1180
|
exports.detectCommandInjection = detectCommandInjection;
|
|
1181
|
+
exports.detectEmailHeaderInjection = detectEmailHeaderInjection;
|
|
1182
|
+
exports.detectGraphqlAbuse = detectGraphqlAbuse;
|
|
1113
1183
|
exports.detectHeaderInjection = detectHeaderInjection;
|
|
1114
1184
|
exports.detectJsonpInjection = detectJsonpInjection;
|
|
1115
1185
|
exports.detectLdapInjection = detectLdapInjection;
|
|
@@ -1119,6 +1189,7 @@ exports.detectPii = detectPii;
|
|
|
1119
1189
|
exports.detectPrototypePollution = detectPrototypePollution;
|
|
1120
1190
|
exports.detectSql = detectSql;
|
|
1121
1191
|
exports.detectSsti = detectSsti;
|
|
1192
|
+
exports.detectXpathInjection = detectXpathInjection;
|
|
1122
1193
|
exports.detectXss = detectXss;
|
|
1123
1194
|
exports.detectXxe = detectXxe;
|
|
1124
1195
|
exports.encodeForAttribute = encodeForAttribute;
|
|
@@ -1129,12 +1200,14 @@ exports.encodeForUrl = encodeForUrl;
|
|
|
1129
1200
|
exports.encodeHtmlEntities = encodeHtmlEntities;
|
|
1130
1201
|
exports.getDangerousOperators = getDangerousOperators;
|
|
1131
1202
|
exports.getDangerousProtoKeys = getDangerousProtoKeys;
|
|
1203
|
+
exports.inspectGraphqlQuery = inspectGraphqlQuery;
|
|
1132
1204
|
exports.isDangerousNoSqlKey = isDangerousNoSqlKey;
|
|
1133
1205
|
exports.isDangerousProtoKey = isDangerousProtoKey;
|
|
1134
1206
|
exports.isPlainObject = isPlainObject;
|
|
1135
1207
|
exports.redactObjectPii = redactObjectPii;
|
|
1136
1208
|
exports.redactPii = redactPii;
|
|
1137
1209
|
exports.sanitizeCommand = sanitizeCommand;
|
|
1210
|
+
exports.sanitizeEmailHeader = sanitizeEmailHeader;
|
|
1138
1211
|
exports.sanitizeHeaderValue = sanitizeHeaderValue;
|
|
1139
1212
|
exports.sanitizeHeaders = sanitizeHeaders;
|
|
1140
1213
|
exports.sanitizeJsonpCallback = sanitizeJsonpCallback;
|
|
@@ -1145,6 +1218,7 @@ exports.sanitizePath = sanitizePath;
|
|
|
1145
1218
|
exports.sanitizeSql = sanitizeSql;
|
|
1146
1219
|
exports.sanitizeSsti = sanitizeSsti;
|
|
1147
1220
|
exports.sanitizeString = sanitizeString;
|
|
1221
|
+
exports.sanitizeXpath = sanitizeXpath;
|
|
1148
1222
|
exports.sanitizeXss = sanitizeXss;
|
|
1149
1223
|
exports.sanitizeXxe = sanitizeXxe;
|
|
1150
1224
|
exports.scanObjectPii = scanObjectPii;
|