@arcis/node 1.5.2 → 1.6.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.
Files changed (66) hide show
  1. package/README.md +48 -7
  2. package/dist/astro/index.js.map +1 -1
  3. package/dist/astro/index.mjs.map +1 -1
  4. package/dist/bun/index.js.map +1 -1
  5. package/dist/bun/index.mjs.map +1 -1
  6. package/dist/core/constants.d.ts +2 -2
  7. package/dist/core/constants.d.ts.map +1 -1
  8. package/dist/core/index.js +19 -1
  9. package/dist/core/index.js.map +1 -1
  10. package/dist/core/index.mjs +19 -1
  11. package/dist/core/index.mjs.map +1 -1
  12. package/dist/fastify/index.js.map +1 -1
  13. package/dist/fastify/index.mjs.map +1 -1
  14. package/dist/hono/index.js.map +1 -1
  15. package/dist/hono/index.mjs.map +1 -1
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +407 -8
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +407 -9
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/koa/index.js.map +1 -1
  23. package/dist/koa/index.mjs.map +1 -1
  24. package/dist/logging/index.js.map +1 -1
  25. package/dist/logging/index.mjs.map +1 -1
  26. package/dist/middleware/correlation.d.ts +87 -0
  27. package/dist/middleware/correlation.d.ts.map +1 -0
  28. package/dist/middleware/graphql.d.ts.map +1 -1
  29. package/dist/middleware/index.d.ts +3 -1
  30. package/dist/middleware/index.d.ts.map +1 -1
  31. package/dist/middleware/index.js +366 -8
  32. package/dist/middleware/index.js.map +1 -1
  33. package/dist/middleware/index.mjs +366 -9
  34. package/dist/middleware/index.mjs.map +1 -1
  35. package/dist/middleware/protect.d.ts +32 -0
  36. package/dist/middleware/protect.d.ts.map +1 -1
  37. package/dist/nestjs/index.js +55 -2
  38. package/dist/nestjs/index.js.map +1 -1
  39. package/dist/nestjs/index.mjs +55 -2
  40. package/dist/nestjs/index.mjs.map +1 -1
  41. package/dist/nextjs/index.js.map +1 -1
  42. package/dist/nextjs/index.mjs.map +1 -1
  43. package/dist/nuxt/index.js.map +1 -1
  44. package/dist/nuxt/index.mjs.map +1 -1
  45. package/dist/sanitizers/deserialization.d.ts +30 -0
  46. package/dist/sanitizers/deserialization.d.ts.map +1 -0
  47. package/dist/sanitizers/graphql.d.ts +20 -3
  48. package/dist/sanitizers/graphql.d.ts.map +1 -1
  49. package/dist/sanitizers/index.d.ts +2 -0
  50. package/dist/sanitizers/index.d.ts.map +1 -1
  51. package/dist/sanitizers/index.js +150 -7
  52. package/dist/sanitizers/index.js.map +1 -1
  53. package/dist/sanitizers/index.mjs +149 -8
  54. package/dist/sanitizers/index.mjs.map +1 -1
  55. package/dist/sanitizers/prompt-injection.d.ts.map +1 -1
  56. package/dist/sanitizers/sanitize.d.ts +0 -20
  57. package/dist/sanitizers/sanitize.d.ts.map +1 -1
  58. package/dist/stores/index.js.map +1 -1
  59. package/dist/stores/index.mjs.map +1 -1
  60. package/dist/sveltekit/index.js.map +1 -1
  61. package/dist/sveltekit/index.mjs.map +1 -1
  62. package/dist/validation/index.js +55 -2
  63. package/dist/validation/index.js.map +1 -1
  64. package/dist/validation/index.mjs +55 -2
  65. package/dist/validation/index.mjs.map +1 -1
  66. package/package.json +2 -2
@@ -36,6 +36,32 @@ import { type CsrfOptions } from './csrf';
36
36
  import type { CorsOptions } from './cors';
37
37
  import { type SignupProtectionOptions } from './signup-protection';
38
38
  import type { RateLimitOptions, SanitizeOptions } from '../core/types';
39
+ import { CorrelationWindow } from './correlation';
40
+ /**
41
+ * Per-protect-helper correlation-window wiring (improvements.md §1.4).
42
+ *
43
+ * Pass an instance of `CorrelationWindow` plus the vector tag this
44
+ * route represents ("login" / "signup" / "api"). The middleware
45
+ * records every request in the window and refuses the request when
46
+ * the window flags the IP as a scanner / credential stuffer / race
47
+ * probe. Detection-only otherwise.
48
+ *
49
+ * Pull-out fields:
50
+ * - `usernameField`: body key whose value is the distinct-value
51
+ * tracked for credential-stuffing detection. Defaults to
52
+ * `'username'`.
53
+ * - `route`: route label recorded in the window (so cross-route
54
+ * aggregation is meaningful). Defaults to the request path.
55
+ * - `statusCode` / `message`: response shape on a correlation block.
56
+ */
57
+ export interface CorrelationOptions {
58
+ window: CorrelationWindow;
59
+ vector?: string;
60
+ usernameField?: string;
61
+ route?: string;
62
+ statusCode?: number;
63
+ message?: string;
64
+ }
39
65
  /**
40
66
  * Per-layer override knob: pass `false` to disable, an options object
41
67
  * to merge into the layer's defaults, or omit to accept the helper's
@@ -47,6 +73,8 @@ export interface ProtectLoginOptions {
47
73
  bot?: LayerOverride<BotProtectionOptions>;
48
74
  csrf?: LayerOverride<CsrfOptions>;
49
75
  sanitize?: LayerOverride<SanitizeOptions>;
76
+ /** Optional correlation-window wiring (improvements.md §1.4). */
77
+ correlation?: CorrelationOptions;
50
78
  }
51
79
  export interface ProtectSignupOptions {
52
80
  rateLimit?: LayerOverride<RateLimitOptions>;
@@ -54,12 +82,16 @@ export interface ProtectSignupOptions {
54
82
  sanitize?: LayerOverride<SanitizeOptions>;
55
83
  /** signupProtection options (email-style validation, disposable-mail block, etc.). */
56
84
  signup?: LayerOverride<SignupProtectionOptions>;
85
+ /** Optional correlation-window wiring (improvements.md §1.4). */
86
+ correlation?: CorrelationOptions;
57
87
  }
58
88
  export interface ProtectApiOptions {
59
89
  rateLimit?: LayerOverride<RateLimitOptions>;
60
90
  /** CORS is required to take an Origin/Methods config — no default origin. */
61
91
  cors?: LayerOverride<CorsOptions>;
62
92
  sanitize?: LayerOverride<SanitizeOptions>;
93
+ /** Optional correlation-window wiring (improvements.md §1.4). */
94
+ correlation?: CorrelationOptions;
63
95
  }
64
96
  /**
65
97
  * Login endpoints get the strictest defaults: 5 req/min/IP, deny
@@ -1 +1 @@
1
- {"version":3,"file":"protect.d.ts","sourceRoot":"","sources":["../../src/middleware/protect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAErF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEvE;;;;GAIG;AACH,KAAK,aAAa,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;AAE9C,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAC1C,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAC1C,QAAQ,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC1C,sFAAsF;IACtF,MAAM,CAAC,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAC;CACjD;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,6EAA6E;IAC7E,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;CAC3C;AAaD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,cAAc,EAAE,CAoBhF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,cAAc,EAAE,CAoBlF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,EAAE,CAgB5E"}
1
+ {"version":3,"file":"protect.d.ts","sourceRoot":"","sources":["../../src/middleware/protect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAErF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmDD;;;;GAIG;AACH,KAAK,aAAa,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC;AAE9C,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAC1C,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC1C,iEAAiE;IACjE,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;IAC1C,QAAQ,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC1C,sFAAsF;IACtF,MAAM,CAAC,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAC;IAChD,iEAAiE;IACjE,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC5C,6EAA6E;IAC7E,IAAI,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC1C,iEAAiE;IACjE,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAaD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,cAAc,EAAE,CA0BhF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,cAAc,EAAE,CA0BlF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,EAAE,CAsB5E"}
@@ -139,7 +139,16 @@ var SQL_PATTERNS = [
139
139
  /** Time-based blind: PostgreSQL pg_sleep() */
140
140
  /\bpg_sleep\s*\(/gi,
141
141
  /** Time-based blind: MSSQL WAITFOR DELAY */
142
- /\bWAITFOR\s+DELAY\b/gi
142
+ /\bWAITFOR\s+DELAY\b/gi,
143
+ /**
144
+ * Oracle DBMS_* stdlib packages used for time-based blind SQLi
145
+ * (DBMS_LOCK.SLEEP, DBMS_PIPE.RECEIVE_MESSAGE) and other Oracle
146
+ * abuse paths. No legitimate user input contains these. Mirrors
147
+ * `sqli-oracle-dbms-packages` in packages/core/patterns.json —
148
+ * improvements.md §1.1.e Q3. Must stay in sync until Node
149
+ * migrates to patterns.json-at-runtime (planned v1.7).
150
+ */
151
+ /\bDBMS_(?:LOCK|PIPE|UTILITY|XSLPROCESSOR|JAVA|OUTPUT|SCHEDULER)\b/gi
143
152
  ];
144
153
  var PATH_PATTERNS = [
145
154
  /** Unix path traversal */
@@ -177,6 +186,15 @@ var COMMAND_PATTERNS = [
177
186
  /[;&|`]/g,
178
187
  /** Command substitution: $( ... ) — matched as a pair to reduce false positives */
179
188
  /\$\(/g,
189
+ /**
190
+ * POSIX shell IFS-substitution: ${IFS} or ${IFS%??}.
191
+ * Attackers use this to inject spaces past metacharacter filters
192
+ * in payloads like `;cat${IFS}/etc/passwd`. Mirrors
193
+ * `cmdi-ifs-bypass` in packages/core/patterns.json — improvements.md
194
+ * §1.1.e Q5. Must stay in sync until Node migrates to
195
+ * patterns.json-at-runtime (planned v1.7).
196
+ */
197
+ /\$\{IFS(?:%[^}]*)?\}/g,
180
198
  /** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
181
199
  /%0[0-9a-f]/gi
182
200
  ];
@@ -947,6 +965,40 @@ function detectHeaderInjection(input) {
947
965
  }
948
966
 
949
967
  // src/sanitizers/sanitize.ts
968
+ function multiDecode(value, maxPasses = 4) {
969
+ for (let i = 0; i < maxPasses; i++) {
970
+ const prev = value;
971
+ try {
972
+ value = decodeURIComponent(value);
973
+ } catch {
974
+ }
975
+ value = htmlEntityDecode(value);
976
+ if (value === prev) break;
977
+ }
978
+ return value;
979
+ }
980
+ function htmlEntityDecode(s) {
981
+ s = s.replace(/&#(\d+);/g, (_m, n) => {
982
+ const code = parseInt(n, 10);
983
+ return Number.isFinite(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : _m;
984
+ });
985
+ s = s.replace(/&#x([0-9a-fA-F]+);/g, (_m, h) => {
986
+ const code = parseInt(h, 16);
987
+ return Number.isFinite(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : _m;
988
+ });
989
+ const named = {
990
+ "&lt;": "<",
991
+ "&gt;": ">",
992
+ "&amp;": "&",
993
+ "&quot;": '"',
994
+ "&apos;": "'",
995
+ "&nbsp;": " "
996
+ };
997
+ for (const [entity, ch] of Object.entries(named)) {
998
+ s = s.split(entity).join(ch);
999
+ }
1000
+ return s;
1001
+ }
950
1002
  function sanitizeString(value, options = {}) {
951
1003
  if (typeof value !== "string") return value;
952
1004
  const maxSize = options.maxSize ?? INPUT.DEFAULT_MAX_SIZE;
@@ -954,7 +1006,8 @@ function sanitizeString(value, options = {}) {
954
1006
  throw new InputTooLargeError(maxSize, value.length);
955
1007
  }
956
1008
  const reject = options.mode === "reject";
957
- let result = value;
1009
+ let result = value.normalize("NFKC");
1010
+ result = multiDecode(result);
958
1011
  if (options.sql !== false) {
959
1012
  if (reject) {
960
1013
  if (detectSql(result)) {