@arcis/node 1.4.2 → 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/dist/core/constants.d.ts +1 -1
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- 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 +405 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +402 -21
- 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 +609 -9
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +608 -10
- 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/index.js +26 -8
- package/dist/sanitizers/index.js.map +1 -1
- package/dist/sanitizers/index.mjs +26 -8
- package/dist/sanitizers/index.mjs.map +1 -1
- package/dist/sanitizers/pii.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.map +1 -1
- package/dist/stores/index.mjs.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 +3 -0
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/index.mjs +3 -0
- package/dist/validation/index.mjs.map +1 -1
- package/package.json +7 -1
|
@@ -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"}
|
package/dist/sanitizers/index.js
CHANGED
|
@@ -42,6 +42,9 @@ var XSS_REMOVE_PATTERNS = [
|
|
|
42
42
|
/<script[^>]*>[\s\S]*?<\/script>/gi,
|
|
43
43
|
/** Standalone/unclosed script tags */
|
|
44
44
|
/<script[^>]*>/gi,
|
|
45
|
+
/** style — CSS expression() and behavior: attacks (IE-era but still relevant) */
|
|
46
|
+
/<style[^>]*>[\s\S]*?<\/style>/gi,
|
|
47
|
+
/<style[^>]*/gi,
|
|
45
48
|
/** iframe — full block and partial/unclosed */
|
|
46
49
|
/<iframe[^>]*>[\s\S]*?<\/iframe>/gi,
|
|
47
50
|
/<iframe[^>]*/gi,
|
|
@@ -590,17 +593,19 @@ var SSTI_REMOVE_PATTERNS = [
|
|
|
590
593
|
/** Jinja2 / Twig: {{ ... }} — always strip (not valid in any JS context) */
|
|
591
594
|
/\{\{.*?\}\}/g,
|
|
592
595
|
/**
|
|
593
|
-
* Freemarker / Spring EL: ${...} —
|
|
594
|
-
*
|
|
596
|
+
* Freemarker / Spring EL: ${...} — strip when expression contains operators,
|
|
597
|
+
* method calls, or Python dunder patterns (sandbox escape).
|
|
595
598
|
* Bare ${name} and ${user.name} are left intact (JS template literal syntax).
|
|
596
599
|
*/
|
|
600
|
+
/\$\{[^}]*__\w+__[^}]*\}/g,
|
|
597
601
|
/\$\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
598
602
|
/** ERB / EJS: <%= ... %> */
|
|
599
603
|
/<%[=\-]?.*?%>/gs,
|
|
600
604
|
/**
|
|
601
|
-
* Pug / Jade: #{...} — same narrowing as ${ above.
|
|
605
|
+
* Pug / Jade: #{...} — same narrowing as ${ above, plus dunder detection.
|
|
602
606
|
* #{name} output expressions are left intact.
|
|
603
607
|
*/
|
|
608
|
+
/#\{[^}]*__\w+__[^}]*\}/g,
|
|
604
609
|
/#\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
605
610
|
/** Python dunder sandbox escape — always strip */
|
|
606
611
|
/__(?:class|mro|subclasses|globals|builtins|import)__/gi
|
|
@@ -649,6 +654,8 @@ function detectSsti(input) {
|
|
|
649
654
|
}
|
|
650
655
|
|
|
651
656
|
// src/sanitizers/xxe.ts
|
|
657
|
+
var MAX_XXE_INPUT_BYTES = 1e6;
|
|
658
|
+
var MAX_ENTITY_REFERENCES = 64;
|
|
652
659
|
var XXE_DETECT_PATTERNS = [
|
|
653
660
|
/** DOCTYPE declaration */
|
|
654
661
|
/<!DOCTYPE\b/gi,
|
|
@@ -678,6 +685,19 @@ function sanitizeXxe(input, collectThreats = false) {
|
|
|
678
685
|
const threats = [];
|
|
679
686
|
let value = input;
|
|
680
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
|
+
}
|
|
681
701
|
for (const pattern of XXE_REMOVE_PATTERNS) {
|
|
682
702
|
pattern.lastIndex = 0;
|
|
683
703
|
if (pattern.test(value)) {
|
|
@@ -715,12 +735,10 @@ function detectXxe(input) {
|
|
|
715
735
|
}
|
|
716
736
|
|
|
717
737
|
// src/sanitizers/jsonp.ts
|
|
718
|
-
var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.
|
|
738
|
+
var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/;
|
|
719
739
|
var DANGEROUS_CALLBACK_PATTERNS = [
|
|
720
|
-
|
|
740
|
+
/\.\./
|
|
721
741
|
// prototype chain traversal
|
|
722
|
-
/\[\s*\]/
|
|
723
|
-
// empty bracket access
|
|
724
742
|
];
|
|
725
743
|
function sanitizeJsonpCallback(callback, maxLength = 128) {
|
|
726
744
|
if (typeof callback !== "string" || callback.length === 0) {
|
|
@@ -805,7 +823,7 @@ function detectHeaderInjection(input) {
|
|
|
805
823
|
|
|
806
824
|
// src/sanitizers/pii.ts
|
|
807
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;
|
|
808
|
-
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;
|
|
809
827
|
var CREDIT_CARD_RE = /\b(?:\d[ -]*?){13,19}\b/g;
|
|
810
828
|
var SSN_RE = /\b\d{3}[-\s]\d{2}[-\s]\d{4}\b/g;
|
|
811
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;
|