@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.
Files changed (51) hide show
  1. package/dist/core/constants.d.ts +1 -1
  2. package/dist/core/constants.d.ts.map +1 -1
  3. package/dist/core/index.js.map +1 -1
  4. package/dist/core/index.mjs.map +1 -1
  5. package/dist/core/types.d.ts +6 -0
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/index.d.ts +4 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +405 -20
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +402 -21
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/logging/index.js.map +1 -1
  14. package/dist/logging/index.mjs.map +1 -1
  15. package/dist/middleware/bot-detection.d.ts.map +1 -1
  16. package/dist/middleware/cookies.d.ts.map +1 -1
  17. package/dist/middleware/csrf.d.ts +10 -0
  18. package/dist/middleware/csrf.d.ts.map +1 -1
  19. package/dist/middleware/hpp.d.ts.map +1 -1
  20. package/dist/middleware/index.d.ts +2 -0
  21. package/dist/middleware/index.d.ts.map +1 -1
  22. package/dist/middleware/index.js +609 -9
  23. package/dist/middleware/index.js.map +1 -1
  24. package/dist/middleware/index.mjs +608 -10
  25. package/dist/middleware/index.mjs.map +1 -1
  26. package/dist/middleware/main.d.ts.map +1 -1
  27. package/dist/middleware/rate-limit.d.ts.map +1 -1
  28. package/dist/middleware/signup-protection.d.ts +65 -0
  29. package/dist/middleware/signup-protection.d.ts.map +1 -0
  30. package/dist/middleware/telemetry.d.ts +36 -0
  31. package/dist/middleware/telemetry.d.ts.map +1 -0
  32. package/dist/sanitizers/index.js +26 -8
  33. package/dist/sanitizers/index.js.map +1 -1
  34. package/dist/sanitizers/index.mjs +26 -8
  35. package/dist/sanitizers/index.mjs.map +1 -1
  36. package/dist/sanitizers/pii.d.ts.map +1 -1
  37. package/dist/sanitizers/ssti.d.ts.map +1 -1
  38. package/dist/sanitizers/xxe.d.ts.map +1 -1
  39. package/dist/stores/index.js.map +1 -1
  40. package/dist/stores/index.mjs.map +1 -1
  41. package/dist/telemetry/client.d.ts +60 -0
  42. package/dist/telemetry/client.d.ts.map +1 -0
  43. package/dist/telemetry/index.d.ts +3 -0
  44. package/dist/telemetry/index.d.ts.map +1 -0
  45. package/dist/telemetry/types.d.ts +59 -0
  46. package/dist/telemetry/types.d.ts.map +1 -0
  47. package/dist/validation/index.js +3 -0
  48. package/dist/validation/index.js.map +1 -1
  49. package/dist/validation/index.mjs +3 -0
  50. package/dist/validation/index.mjs.map +1 -1
  51. 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;AAQvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,KAAK,CAAC,OAAO,GAAE,YAAiB,GAAG,oBAAoB,CAuCtE;AAGD,QAAA,MAAM,gBAAgB,EAAY,aAAa,CAAC;AAQhD,OAAO,EAAE,gBAAgB,IAAI,aAAa,EAAE,CAAC;AAC7C,eAAe,gBAAgB,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,CAoIvF;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,0BAAoB,CAAC"}
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"}
@@ -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: ${...} — only strip when the expression contains
594
- * operators (?!*+-/), method calls (), or known-dangerous prefixes.
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;