@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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/dist/core/constants.d.ts +2 -2
  3. package/dist/core/constants.d.ts.map +1 -1
  4. package/dist/core/index.js +11 -3
  5. package/dist/core/index.js.map +1 -1
  6. package/dist/core/index.mjs +11 -3
  7. package/dist/core/index.mjs.map +1 -1
  8. package/dist/core/types.d.ts +6 -0
  9. package/dist/core/types.d.ts.map +1 -1
  10. package/dist/index.d.ts +4 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +527 -63
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.mjs +525 -65
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/logging/index.js.map +1 -1
  17. package/dist/logging/index.mjs.map +1 -1
  18. package/dist/middleware/bot-detection.d.ts.map +1 -1
  19. package/dist/middleware/cookies.d.ts.map +1 -1
  20. package/dist/middleware/csrf.d.ts +10 -0
  21. package/dist/middleware/csrf.d.ts.map +1 -1
  22. package/dist/middleware/hpp.d.ts.map +1 -1
  23. package/dist/middleware/index.d.ts +2 -0
  24. package/dist/middleware/index.d.ts.map +1 -1
  25. package/dist/middleware/index.js +671 -39
  26. package/dist/middleware/index.js.map +1 -1
  27. package/dist/middleware/index.mjs +671 -41
  28. package/dist/middleware/index.mjs.map +1 -1
  29. package/dist/middleware/main.d.ts.map +1 -1
  30. package/dist/middleware/rate-limit.d.ts.map +1 -1
  31. package/dist/middleware/signup-protection.d.ts +65 -0
  32. package/dist/middleware/signup-protection.d.ts.map +1 -0
  33. package/dist/middleware/telemetry.d.ts +36 -0
  34. package/dist/middleware/telemetry.d.ts.map +1 -0
  35. package/dist/sanitizers/encode.d.ts.map +1 -1
  36. package/dist/sanitizers/index.d.ts +1 -0
  37. package/dist/sanitizers/index.d.ts.map +1 -1
  38. package/dist/sanitizers/index.js +113 -37
  39. package/dist/sanitizers/index.js.map +1 -1
  40. package/dist/sanitizers/index.mjs +111 -38
  41. package/dist/sanitizers/index.mjs.map +1 -1
  42. package/dist/sanitizers/ldap.d.ts +42 -0
  43. package/dist/sanitizers/ldap.d.ts.map +1 -0
  44. package/dist/sanitizers/path.d.ts.map +1 -1
  45. package/dist/sanitizers/pii.d.ts.map +1 -1
  46. package/dist/sanitizers/sanitize.d.ts.map +1 -1
  47. package/dist/sanitizers/ssti.d.ts.map +1 -1
  48. package/dist/sanitizers/xxe.d.ts.map +1 -1
  49. package/dist/stores/index.js +21 -1
  50. package/dist/stores/index.js.map +1 -1
  51. package/dist/stores/index.mjs +21 -1
  52. package/dist/stores/index.mjs.map +1 -1
  53. package/dist/stores/memory.d.ts +4 -10
  54. package/dist/stores/memory.d.ts.map +1 -1
  55. package/dist/telemetry/client.d.ts +60 -0
  56. package/dist/telemetry/client.d.ts.map +1 -0
  57. package/dist/telemetry/index.d.ts +3 -0
  58. package/dist/telemetry/index.d.ts.map +1 -0
  59. package/dist/telemetry/types.d.ts +59 -0
  60. package/dist/telemetry/types.d.ts.map +1 -0
  61. package/dist/validation/index.js +41 -21
  62. package/dist/validation/index.js.map +1 -1
  63. package/dist/validation/index.mjs +41 -21
  64. package/dist/validation/index.mjs.map +1 -1
  65. package/package.json +8 -2
@@ -25,13 +25,24 @@ var XSS_PATTERNS = [
25
25
  /** URL-encoded script tags */
26
26
  /%3Cscript/gi,
27
27
  /** SVG with onload */
28
- /<svg[^>]*onload/gi
28
+ /<svg[^>]*onload/gi,
29
+ /** form tags — phishing/credential harvesting via action= redirection */
30
+ /<form[\s>]/gi,
31
+ /** meta tags — http-equiv refresh redirects or CSP bypass */
32
+ /<meta[\s>]/gi,
33
+ /** base href hijacking — redirects all relative URLs to attacker domain */
34
+ /<base[\s>]/gi,
35
+ /** link tag injection — stylesheet or preload CSRF attacks */
36
+ /<link[\s>]/gi
29
37
  ];
30
38
  var XSS_REMOVE_PATTERNS = [
31
39
  /** Full script blocks (content + tags) */
32
40
  /<script[^>]*>[\s\S]*?<\/script>/gi,
33
41
  /** Standalone/unclosed script tags */
34
42
  /<script[^>]*>/gi,
43
+ /** style — CSS expression() and behavior: attacks (IE-era but still relevant) */
44
+ /<style[^>]*>[\s\S]*?<\/style>/gi,
45
+ /<style[^>]*/gi,
35
46
  /** iframe — full block and partial/unclosed */
36
47
  /<iframe[^>]*>[\s\S]*?<\/iframe>/gi,
37
48
  /<iframe[^>]*/gi,
@@ -52,7 +63,15 @@ var XSS_REMOVE_PATTERNS = [
52
63
  /javascript\s*:/gi,
53
64
  /vbscript\s*:/gi,
54
65
  /** data: URIs with HTML/script content */
55
- /data\s*:\s*text\/html[^>\s]*/gi
66
+ /data\s*:\s*text\/html[^>\s]*/gi,
67
+ /** form tag injection — phishing via action= redirection */
68
+ /<form[\s>][^>]*/gi,
69
+ /** meta tag injection — http-equiv refresh or CSP bypass */
70
+ /<meta[\s>][^>]*/gi,
71
+ /** base href hijacking */
72
+ /<base[\s>][^>]*/gi,
73
+ /** link tag injection — stylesheet or preload attacks */
74
+ /<link[\s>][^>]*/gi
56
75
  ];
57
76
  var SQL_PATTERNS = [
58
77
  /** SQL keywords */
@@ -116,8 +135,8 @@ var COMMAND_PATTERNS = [
116
135
  /[;&|`]/g,
117
136
  /** Command substitution: $( ... ) — matched as a pair to reduce false positives */
118
137
  /\$\(/g,
119
- /** URL-encoded newline/carriage-return injection (%0a, %0d) */
120
- /%0[ad]/gi
138
+ /** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
139
+ /%0[0-9a-f]/gi
121
140
  ];
122
141
  var DANGEROUS_PROTO_KEYS = /* @__PURE__ */ new Set([
123
142
  "__proto__",
@@ -321,26 +340,31 @@ function sanitizePath(input, collectThreats = false) {
321
340
  const threats = [];
322
341
  let value = input;
323
342
  let wasSanitized = false;
324
- for (const pattern of PATH_PATTERNS) {
325
- pattern.lastIndex = 0;
326
- if (pattern.test(value)) {
343
+ value = value.normalize("NFKC");
344
+ let prev;
345
+ do {
346
+ prev = value;
347
+ for (const pattern of PATH_PATTERNS) {
327
348
  pattern.lastIndex = 0;
328
- if (collectThreats) {
329
- const matches = value.match(pattern);
330
- if (matches) {
331
- for (const match of matches) {
332
- threats.push({
333
- type: "path_traversal",
334
- pattern: pattern.source,
335
- original: match
336
- });
349
+ if (pattern.test(value)) {
350
+ pattern.lastIndex = 0;
351
+ if (collectThreats) {
352
+ const matches = value.match(pattern);
353
+ if (matches) {
354
+ for (const match of matches) {
355
+ threats.push({
356
+ type: "path_traversal",
357
+ pattern: pattern.source,
358
+ original: match
359
+ });
360
+ }
337
361
  }
338
362
  }
363
+ value = value.replace(pattern, "");
364
+ wasSanitized = true;
339
365
  }
340
- value = value.replace(pattern, "");
341
- wasSanitized = true;
342
366
  }
343
- }
367
+ } while (value !== prev);
344
368
  if (collectThreats) {
345
369
  return { value, wasSanitized, threats };
346
370
  }
@@ -348,9 +372,10 @@ function sanitizePath(input, collectThreats = false) {
348
372
  }
349
373
  function detectPathTraversal(input) {
350
374
  if (typeof input !== "string") return false;
375
+ const normalized = input.normalize("NFKC");
351
376
  for (const pattern of PATH_PATTERNS) {
352
377
  pattern.lastIndex = 0;
353
- if (pattern.test(input)) {
378
+ if (pattern.test(normalized)) {
354
379
  return true;
355
380
  }
356
381
  }
@@ -408,7 +433,7 @@ function sanitizeString(value, options = {}) {
408
433
  if (value.length > maxSize) {
409
434
  throw new InputTooLargeError(maxSize, value.length);
410
435
  }
411
- const reject = options.mode !== "sanitize";
436
+ const reject = options.mode === "reject";
412
437
  let result = value;
413
438
  if (options.sql !== false) {
414
439
  if (reject) {
@@ -563,10 +588,24 @@ var SSTI_DETECT_PATTERNS = [
563
588
  /\{\{\s*(?:self|request|lipsum|cycler|joiner|namespace|range)\b/gi
564
589
  ];
565
590
  var SSTI_REMOVE_PATTERNS = [
591
+ /** Jinja2 / Twig: {{ ... }} — always strip (not valid in any JS context) */
566
592
  /\{\{.*?\}\}/g,
567
- /\$\{.*?\}/g,
593
+ /**
594
+ * Freemarker / Spring EL: ${...} — strip when expression contains operators,
595
+ * method calls, or Python dunder patterns (sandbox escape).
596
+ * Bare ${name} and ${user.name} are left intact (JS template literal syntax).
597
+ */
598
+ /\$\{[^}]*__\w+__[^}]*\}/g,
599
+ /\$\{[^}]*[?!()*+\-/][^}]*\}/g,
600
+ /** ERB / EJS: <%= ... %> */
568
601
  /<%[=\-]?.*?%>/gs,
569
- /#\{.*?\}/g,
602
+ /**
603
+ * Pug / Jade: #{...} — same narrowing as ${ above, plus dunder detection.
604
+ * #{name} output expressions are left intact.
605
+ */
606
+ /#\{[^}]*__\w+__[^}]*\}/g,
607
+ /#\{[^}]*[?!()*+\-/][^}]*\}/g,
608
+ /** Python dunder sandbox escape — always strip */
570
609
  /__(?:class|mro|subclasses|globals|builtins|import)__/gi
571
610
  ];
572
611
  function sanitizeSsti(input, collectThreats = false) {
@@ -613,6 +652,8 @@ function detectSsti(input) {
613
652
  }
614
653
 
615
654
  // src/sanitizers/xxe.ts
655
+ var MAX_XXE_INPUT_BYTES = 1e6;
656
+ var MAX_ENTITY_REFERENCES = 64;
616
657
  var XXE_DETECT_PATTERNS = [
617
658
  /** DOCTYPE declaration */
618
659
  /<!DOCTYPE\b/gi,
@@ -642,6 +683,19 @@ function sanitizeXxe(input, collectThreats = false) {
642
683
  const threats = [];
643
684
  let value = input;
644
685
  let wasSanitized = false;
686
+ if (value.length > MAX_XXE_INPUT_BYTES) {
687
+ if (collectThreats) {
688
+ threats.push({ type: "xxe", pattern: "oversize_input", original: `length=${value.length}` });
689
+ }
690
+ return collectThreats ? { value: "", wasSanitized: true, threats } : "";
691
+ }
692
+ const entityRefs = value.match(/&\w+;/g);
693
+ if (entityRefs && entityRefs.length > MAX_ENTITY_REFERENCES) {
694
+ if (collectThreats) {
695
+ threats.push({ type: "xxe", pattern: "entity_expansion", original: `count=${entityRefs.length}` });
696
+ }
697
+ return collectThreats ? { value: "", wasSanitized: true, threats } : "";
698
+ }
645
699
  for (const pattern of XXE_REMOVE_PATTERNS) {
646
700
  pattern.lastIndex = 0;
647
701
  if (pattern.test(value)) {
@@ -679,12 +733,10 @@ function detectXxe(input) {
679
733
  }
680
734
 
681
735
  // src/sanitizers/jsonp.ts
682
- var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.[\]]*$/;
736
+ var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/;
683
737
  var DANGEROUS_CALLBACK_PATTERNS = [
684
- /\.\./,
738
+ /\.\./
685
739
  // prototype chain traversal
686
- /\[\s*\]/
687
- // empty bracket access
688
740
  ];
689
741
  function sanitizeJsonpCallback(callback, maxLength = 128) {
690
742
  if (typeof callback !== "string" || callback.length === 0) {
@@ -769,7 +821,7 @@ function detectHeaderInjection(input) {
769
821
 
770
822
  // src/sanitizers/pii.ts
771
823
  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;
772
- var PHONE_RE = /(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g;
824
+ var PHONE_RE = /(?<!\d)(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?!\d)/g;
773
825
  var CREDIT_CARD_RE = /\b(?:\d[ -]*?){13,19}\b/g;
774
826
  var SSN_RE = /\b\d{3}[-\s]\d{2}[-\s]\d{4}\b/g;
775
827
  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;
@@ -930,16 +982,18 @@ function encodeForAttribute(value) {
930
982
  function encodeForJs(value) {
931
983
  if (!value) return "";
932
984
  let result = "";
933
- for (let i = 0; i < value.length; i++) {
934
- const ch = value.charCodeAt(i);
935
- if (ch >= 48 && ch <= 57 || // 0-9
936
- ch >= 65 && ch <= 90 || // A-Z
937
- ch >= 97 && ch <= 122) {
938
- result += value[i];
939
- } else if (ch < 256) {
940
- result += `\\x${ch.toString(16).toUpperCase().padStart(2, "0")}`;
985
+ for (const char of value) {
986
+ const cp = char.codePointAt(0);
987
+ if (cp >= 48 && cp <= 57 || // 0-9
988
+ cp >= 65 && cp <= 90 || // A-Z
989
+ cp >= 97 && cp <= 122) {
990
+ result += char;
991
+ } else if (cp < 256) {
992
+ result += `\\x${cp.toString(16).toUpperCase().padStart(2, "0")}`;
993
+ } else if (cp <= 65535) {
994
+ result += `\\u${cp.toString(16).toUpperCase().padStart(4, "0")}`;
941
995
  } else {
942
- result += `\\u${ch.toString(16).toUpperCase().padStart(4, "0")}`;
996
+ result += `\\u{${cp.toString(16).toUpperCase()}}`;
943
997
  }
944
998
  }
945
999
  return result;
@@ -966,6 +1020,25 @@ function encodeForCss(value) {
966
1020
  return result;
967
1021
  }
968
1022
 
969
- export { createSanitizer, detectCommandInjection, detectHeaderInjection, detectJsonpInjection, detectNoSqlInjection, detectPathTraversal, detectPii, detectPrototypePollution, detectSql, detectSsti, detectXss, detectXxe, encodeForAttribute, encodeForCss, encodeForHtml, encodeForJs, encodeForUrl, encodeHtmlEntities, getDangerousOperators, getDangerousProtoKeys, isDangerousNoSqlKey, isDangerousProtoKey, isPlainObject, redactObjectPii, redactPii, sanitizeCommand, sanitizeHeaderValue, sanitizeHeaders, sanitizeJsonpCallback, sanitizeObject, sanitizePath, sanitizeSql, sanitizeSsti, sanitizeString, sanitizeXss, sanitizeXxe, scanObjectPii, scanPii };
1023
+ // src/sanitizers/ldap.ts
1024
+ var LDAP_FILTER_CHARS = /[*()\\\x00]/g;
1025
+ var LDAP_DN_CHARS = /[,+<>;"=\/\\\x00*()\x00]/g;
1026
+ var LDAP_DETECT_PATTERN = /[*()\\\x00]/;
1027
+ var LDAP_INJECTION_PATTERN = /\)\s*\(|\*\s*\)\s*\(/;
1028
+ var escapeChar = (char) => "\\" + char.charCodeAt(0).toString(16).padStart(2, "0");
1029
+ function sanitizeLdapFilter(input) {
1030
+ if (typeof input !== "string") return String(input);
1031
+ return input.replace(LDAP_FILTER_CHARS, escapeChar);
1032
+ }
1033
+ function sanitizeLdapDn(input) {
1034
+ if (typeof input !== "string") return String(input);
1035
+ return input.replace(LDAP_DN_CHARS, escapeChar);
1036
+ }
1037
+ function detectLdapInjection(input) {
1038
+ if (typeof input !== "string") return false;
1039
+ return LDAP_DETECT_PATTERN.test(input) || LDAP_INJECTION_PATTERN.test(input);
1040
+ }
1041
+
1042
+ export { createSanitizer, detectCommandInjection, detectHeaderInjection, detectJsonpInjection, detectLdapInjection, detectNoSqlInjection, detectPathTraversal, detectPii, detectPrototypePollution, detectSql, detectSsti, detectXss, detectXxe, encodeForAttribute, encodeForCss, encodeForHtml, encodeForJs, encodeForUrl, encodeHtmlEntities, getDangerousOperators, getDangerousProtoKeys, isDangerousNoSqlKey, isDangerousProtoKey, isPlainObject, redactObjectPii, redactPii, sanitizeCommand, sanitizeHeaderValue, sanitizeHeaders, sanitizeJsonpCallback, sanitizeLdapDn, sanitizeLdapFilter, sanitizeObject, sanitizePath, sanitizeSql, sanitizeSsti, sanitizeString, sanitizeXss, sanitizeXxe, scanObjectPii, scanPii };
970
1043
  //# sourceMappingURL=index.mjs.map
971
1044
  //# sourceMappingURL=index.mjs.map