@arcis/node 1.4.0 → 1.4.2

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 (42) 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/index.js +125 -46
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.mjs +126 -47
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/logging/index.js.map +1 -1
  13. package/dist/logging/index.mjs.map +1 -1
  14. package/dist/middleware/csrf.d.ts.map +1 -1
  15. package/dist/middleware/index.js +62 -30
  16. package/dist/middleware/index.js.map +1 -1
  17. package/dist/middleware/index.mjs +63 -31
  18. package/dist/middleware/index.mjs.map +1 -1
  19. package/dist/middleware/rate-limit.d.ts.map +1 -1
  20. package/dist/sanitizers/encode.d.ts.map +1 -1
  21. package/dist/sanitizers/index.d.ts +1 -0
  22. package/dist/sanitizers/index.d.ts.map +1 -1
  23. package/dist/sanitizers/index.js +90 -32
  24. package/dist/sanitizers/index.js.map +1 -1
  25. package/dist/sanitizers/index.mjs +88 -33
  26. package/dist/sanitizers/index.mjs.map +1 -1
  27. package/dist/sanitizers/ldap.d.ts +42 -0
  28. package/dist/sanitizers/ldap.d.ts.map +1 -0
  29. package/dist/sanitizers/path.d.ts.map +1 -1
  30. package/dist/sanitizers/sanitize.d.ts.map +1 -1
  31. package/dist/sanitizers/ssti.d.ts.map +1 -1
  32. package/dist/stores/index.js +21 -1
  33. package/dist/stores/index.js.map +1 -1
  34. package/dist/stores/index.mjs +21 -1
  35. package/dist/stores/index.mjs.map +1 -1
  36. package/dist/stores/memory.d.ts +4 -10
  37. package/dist/stores/memory.d.ts.map +1 -1
  38. package/dist/validation/index.js +38 -21
  39. package/dist/validation/index.js.map +1 -1
  40. package/dist/validation/index.mjs +38 -21
  41. package/dist/validation/index.mjs.map +1 -1
  42. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { randomBytes } from 'crypto';
1
+ import { randomBytes, timingSafeEqual } from 'crypto';
2
2
 
3
3
  // src/core/constants.ts
4
4
  var INPUT = {
@@ -65,7 +65,15 @@ var XSS_REMOVE_PATTERNS = [
65
65
  /javascript\s*:/gi,
66
66
  /vbscript\s*:/gi,
67
67
  /** data: URIs with HTML/script content */
68
- /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
69
77
  ];
70
78
  var SQL_PATTERNS = [
71
79
  /** SQL keywords */
@@ -129,8 +137,8 @@ var COMMAND_PATTERNS = [
129
137
  /[;&|`]/g,
130
138
  /** Command substitution: $( ... ) — matched as a pair to reduce false positives */
131
139
  /\$\(/g,
132
- /** URL-encoded newline/carriage-return injection (%0a, %0d) */
133
- /%0[ad]/gi
140
+ /** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
141
+ /%0[0-9a-f]/gi
134
142
  ];
135
143
  var DANGEROUS_PROTO_KEYS = /* @__PURE__ */ new Set([
136
144
  "__proto__",
@@ -418,7 +426,24 @@ function createRateLimiter(options = {}) {
418
426
  }
419
427
  next();
420
428
  } catch (error) {
421
- console.error("[arcis] Rate limiter error:", error);
429
+ console.error("[arcis] Rate limiter store error, using in-memory fallback:", error);
430
+ try {
431
+ const key = keyGenerator(req);
432
+ const now = Date.now();
433
+ if (!inMemoryStore[key] || inMemoryStore[key].resetTime < now) {
434
+ inMemoryStore[key] = { count: 1, resetTime: now + windowMs };
435
+ } else {
436
+ inMemoryStore[key].count++;
437
+ }
438
+ const count = inMemoryStore[key].count;
439
+ if (count > max) {
440
+ const resetSeconds = Math.ceil((inMemoryStore[key].resetTime - now) / 1e3);
441
+ res.setHeader("Retry-After", resetSeconds.toString());
442
+ res.status(statusCode).json({ error: message, retryAfter: resetSeconds });
443
+ return;
444
+ }
445
+ } catch {
446
+ }
422
447
  next();
423
448
  }
424
449
  };
@@ -628,26 +653,31 @@ function sanitizePath(input, collectThreats = false) {
628
653
  const threats = [];
629
654
  let value = input;
630
655
  let wasSanitized = false;
631
- for (const pattern of PATH_PATTERNS) {
632
- pattern.lastIndex = 0;
633
- if (pattern.test(value)) {
656
+ value = value.normalize("NFKC");
657
+ let prev;
658
+ do {
659
+ prev = value;
660
+ for (const pattern of PATH_PATTERNS) {
634
661
  pattern.lastIndex = 0;
635
- if (collectThreats) {
636
- const matches = value.match(pattern);
637
- if (matches) {
638
- for (const match of matches) {
639
- threats.push({
640
- type: "path_traversal",
641
- pattern: pattern.source,
642
- original: match
643
- });
662
+ if (pattern.test(value)) {
663
+ pattern.lastIndex = 0;
664
+ if (collectThreats) {
665
+ const matches = value.match(pattern);
666
+ if (matches) {
667
+ for (const match of matches) {
668
+ threats.push({
669
+ type: "path_traversal",
670
+ pattern: pattern.source,
671
+ original: match
672
+ });
673
+ }
644
674
  }
645
675
  }
676
+ value = value.replace(pattern, "");
677
+ wasSanitized = true;
646
678
  }
647
- value = value.replace(pattern, "");
648
- wasSanitized = true;
649
679
  }
650
- }
680
+ } while (value !== prev);
651
681
  if (collectThreats) {
652
682
  return { value, wasSanitized, threats };
653
683
  }
@@ -705,7 +735,7 @@ function sanitizeString(value, options = {}) {
705
735
  if (value.length > maxSize) {
706
736
  throw new InputTooLargeError(maxSize, value.length);
707
737
  }
708
- const reject = options.mode !== "sanitize";
738
+ const reject = options.mode === "reject";
709
739
  let result = value;
710
740
  if (options.sql !== false) {
711
741
  if (reject) {
@@ -1582,11 +1612,9 @@ function generateCsrfToken(length = 32) {
1582
1612
  function validateCsrfToken(cookieToken, requestToken) {
1583
1613
  if (!cookieToken || !requestToken) return false;
1584
1614
  if (cookieToken.length !== requestToken.length) return false;
1585
- let result = 0;
1586
- for (let i = 0; i < cookieToken.length; i++) {
1587
- result |= cookieToken.charCodeAt(i) ^ requestToken.charCodeAt(i);
1588
- }
1589
- return result === 0;
1615
+ const a = Buffer.from(cookieToken);
1616
+ const b = Buffer.from(requestToken);
1617
+ return timingSafeEqual(a, b);
1590
1618
  }
1591
1619
  function getRequestToken(req, headerName, fieldName) {
1592
1620
  const headerToken = req.headers[headerName.toLowerCase()];
@@ -1595,10 +1623,6 @@ function getRequestToken(req, headerName, fieldName) {
1595
1623
  const bodyToken = req.body[fieldName];
1596
1624
  if (typeof bodyToken === "string" && bodyToken) return bodyToken;
1597
1625
  }
1598
- if (req.query && fieldName in req.query) {
1599
- const queryToken = req.query[fieldName];
1600
- if (typeof queryToken === "string" && queryToken) return queryToken;
1601
- }
1602
1626
  return void 0;
1603
1627
  }
1604
1628
  function csrfProtection(options = {}) {
@@ -1681,7 +1705,15 @@ function setCsrfCookie(res, name, token, opts) {
1681
1705
  if (opts.secure) parts.push("Secure");
1682
1706
  parts.push(`SameSite=${opts.sameSite}`);
1683
1707
  if (opts.domain) parts.push(`Domain=${opts.domain}`);
1684
- res.setHeader("Set-Cookie", parts.join("; "));
1708
+ const newCookie = parts.join("; ");
1709
+ const existing = res.getHeader("Set-Cookie");
1710
+ if (existing === void 0) {
1711
+ res.setHeader("Set-Cookie", newCookie);
1712
+ } else if (Array.isArray(existing)) {
1713
+ res.setHeader("Set-Cookie", [...existing, newCookie]);
1714
+ } else {
1715
+ res.setHeader("Set-Cookie", [existing, newCookie]);
1716
+ }
1685
1717
  }
1686
1718
  function escapeRegex(str) {
1687
1719
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");