@arcis/node 1.5.1 → 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
@@ -140,7 +140,16 @@ var SQL_PATTERNS = [
140
140
  /** Time-based blind: PostgreSQL pg_sleep() */
141
141
  /\bpg_sleep\s*\(/gi,
142
142
  /** Time-based blind: MSSQL WAITFOR DELAY */
143
- /\bWAITFOR\s+DELAY\b/gi
143
+ /\bWAITFOR\s+DELAY\b/gi,
144
+ /**
145
+ * Oracle DBMS_* stdlib packages used for time-based blind SQLi
146
+ * (DBMS_LOCK.SLEEP, DBMS_PIPE.RECEIVE_MESSAGE) and other Oracle
147
+ * abuse paths. No legitimate user input contains these. Mirrors
148
+ * `sqli-oracle-dbms-packages` in packages/core/patterns.json —
149
+ * improvements.md §1.1.e Q3. Must stay in sync until Node
150
+ * migrates to patterns.json-at-runtime (planned v1.7).
151
+ */
152
+ /\bDBMS_(?:LOCK|PIPE|UTILITY|XSLPROCESSOR|JAVA|OUTPUT|SCHEDULER)\b/gi
144
153
  ];
145
154
  var PATH_PATTERNS = [
146
155
  /** Unix path traversal */
@@ -178,6 +187,15 @@ var COMMAND_PATTERNS = [
178
187
  /[;&|`]/g,
179
188
  /** Command substitution: $( ... ) — matched as a pair to reduce false positives */
180
189
  /\$\(/g,
190
+ /**
191
+ * POSIX shell IFS-substitution: ${IFS} or ${IFS%??}.
192
+ * Attackers use this to inject spaces past metacharacter filters
193
+ * in payloads like `;cat${IFS}/etc/passwd`. Mirrors
194
+ * `cmdi-ifs-bypass` in packages/core/patterns.json — improvements.md
195
+ * §1.1.e Q5. Must stay in sync until Node migrates to
196
+ * patterns.json-at-runtime (planned v1.7).
197
+ */
198
+ /\$\{IFS(?:%[^}]*)?\}/g,
181
199
  /** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
182
200
  /%0[0-9a-f]/gi
183
201
  ];
@@ -979,6 +997,40 @@ function detectHeaderInjection(input) {
979
997
  }
980
998
 
981
999
  // src/sanitizers/sanitize.ts
1000
+ function multiDecode(value, maxPasses = 4) {
1001
+ for (let i = 0; i < maxPasses; i++) {
1002
+ const prev = value;
1003
+ try {
1004
+ value = decodeURIComponent(value);
1005
+ } catch {
1006
+ }
1007
+ value = htmlEntityDecode(value);
1008
+ if (value === prev) break;
1009
+ }
1010
+ return value;
1011
+ }
1012
+ function htmlEntityDecode(s) {
1013
+ s = s.replace(/&#(\d+);/g, (_m, n) => {
1014
+ const code = parseInt(n, 10);
1015
+ return Number.isFinite(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : _m;
1016
+ });
1017
+ s = s.replace(/&#x([0-9a-fA-F]+);/g, (_m, h) => {
1018
+ const code = parseInt(h, 16);
1019
+ return Number.isFinite(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : _m;
1020
+ });
1021
+ const named = {
1022
+ "&lt;": "<",
1023
+ "&gt;": ">",
1024
+ "&amp;": "&",
1025
+ "&quot;": '"',
1026
+ "&apos;": "'",
1027
+ "&nbsp;": " "
1028
+ };
1029
+ for (const [entity, ch] of Object.entries(named)) {
1030
+ s = s.split(entity).join(ch);
1031
+ }
1032
+ return s;
1033
+ }
982
1034
  function sanitizeString(value, options = {}) {
983
1035
  if (typeof value !== "string") return value;
984
1036
  const maxSize = options.maxSize ?? INPUT.DEFAULT_MAX_SIZE;
@@ -986,7 +1038,8 @@ function sanitizeString(value, options = {}) {
986
1038
  throw new InputTooLargeError(maxSize, value.length);
987
1039
  }
988
1040
  const reject = options.mode === "reject";
989
- let result = value;
1041
+ let result = value.normalize("NFKC");
1042
+ result = multiDecode(result);
990
1043
  if (options.sql !== false) {
991
1044
  if (reject) {
992
1045
  if (detectSql(result)) {
@@ -1145,7 +1198,9 @@ function createSanitizer(options = {}) {
1145
1198
  var DEFAULTS = {
1146
1199
  maxDepth: 10,
1147
1200
  maxLength: 1e4,
1148
- blockIntrospection: true
1201
+ blockIntrospection: true,
1202
+ maxAliases: 50,
1203
+ blockFragmentCycles: true
1149
1204
  };
1150
1205
  var INTROSPECTION_PATTERN = /\b__(schema|type|typeKind|directive)\b/;
1151
1206
  function computeDepth(query) {
@@ -1162,22 +1217,87 @@ function computeDepth(query) {
1162
1217
  }
1163
1218
  return max;
1164
1219
  }
1220
+ var ALIAS_PATTERN = /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
1221
+ var FRAGMENT_DEF_PATTERN = /\bfragment\s+([a-zA-Z_][a-zA-Z0-9_]*)\s+on\s+[a-zA-Z_][a-zA-Z0-9_]*\s*\{/g;
1222
+ var FRAGMENT_SPREAD_PATTERN = /\.\.\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
1223
+ function countAliases(query) {
1224
+ let n = 0;
1225
+ ALIAS_PATTERN.lastIndex = 0;
1226
+ while (ALIAS_PATTERN.exec(query) !== null) n++;
1227
+ return n;
1228
+ }
1229
+ function hasFragmentCycle(query) {
1230
+ const deps = /* @__PURE__ */ new Map();
1231
+ FRAGMENT_DEF_PATTERN.lastIndex = 0;
1232
+ let match;
1233
+ while ((match = FRAGMENT_DEF_PATTERN.exec(query)) !== null) {
1234
+ const name = match[1];
1235
+ const bodyStart = match.index + match[0].length;
1236
+ let depth = 1;
1237
+ let i = bodyStart;
1238
+ while (i < query.length && depth > 0) {
1239
+ const ch = query[i];
1240
+ if (ch === "{") depth++;
1241
+ else if (ch === "}") depth--;
1242
+ i++;
1243
+ }
1244
+ const bodyEnd = depth === 0 ? i - 1 : i;
1245
+ const body = query.slice(bodyStart, bodyEnd);
1246
+ const spreads = /* @__PURE__ */ new Set();
1247
+ FRAGMENT_SPREAD_PATTERN.lastIndex = 0;
1248
+ let sm;
1249
+ while ((sm = FRAGMENT_SPREAD_PATTERN.exec(body)) !== null) {
1250
+ spreads.add(sm[1]);
1251
+ }
1252
+ deps.set(name, spreads);
1253
+ }
1254
+ if (deps.size === 0) return false;
1255
+ const WHITE = 0;
1256
+ const GRAY = 1;
1257
+ const BLACK = 2;
1258
+ const color = /* @__PURE__ */ new Map();
1259
+ for (const name of deps.keys()) color.set(name, WHITE);
1260
+ function visit(name) {
1261
+ if (color.get(name) === GRAY) return true;
1262
+ if (color.get(name) === BLACK) return false;
1263
+ if (!deps.has(name)) return false;
1264
+ color.set(name, GRAY);
1265
+ for (const child of deps.get(name)) {
1266
+ if (visit(child)) return true;
1267
+ }
1268
+ color.set(name, BLACK);
1269
+ return false;
1270
+ }
1271
+ for (const name of deps.keys()) {
1272
+ if (visit(name)) return true;
1273
+ }
1274
+ return false;
1275
+ }
1165
1276
  function inspectGraphqlQuery(query, options = {}) {
1166
1277
  const maxDepth = options.maxDepth ?? DEFAULTS.maxDepth;
1167
1278
  const maxLength = options.maxLength ?? DEFAULTS.maxLength;
1168
1279
  const blockIntrospection = options.blockIntrospection ?? DEFAULTS.blockIntrospection;
1280
+ const maxAliases = options.maxAliases ?? DEFAULTS.maxAliases;
1281
+ const blockFragmentCycles = options.blockFragmentCycles ?? DEFAULTS.blockFragmentCycles;
1169
1282
  const length = query.length;
1170
1283
  const depth = computeDepth(query);
1284
+ const aliases = countAliases(query);
1171
1285
  if (depth > maxDepth) {
1172
- return { blocked: true, reason: "depth", depth, length };
1286
+ return { blocked: true, reason: "depth", depth, length, aliases };
1173
1287
  }
1174
1288
  if (blockIntrospection && INTROSPECTION_PATTERN.test(query)) {
1175
- return { blocked: true, reason: "introspection", depth, length };
1289
+ return { blocked: true, reason: "introspection", depth, length, aliases };
1290
+ }
1291
+ if (aliases > maxAliases) {
1292
+ return { blocked: true, reason: "aliases", depth, length, aliases };
1293
+ }
1294
+ if (blockFragmentCycles && hasFragmentCycle(query)) {
1295
+ return { blocked: true, reason: "fragment_cycle", depth, length, aliases };
1176
1296
  }
1177
1297
  if (length > maxLength) {
1178
- return { blocked: true, reason: "length", depth, length };
1298
+ return { blocked: true, reason: "length", depth, length, aliases };
1179
1299
  }
1180
- return { blocked: false, depth, length };
1300
+ return { blocked: false, depth, length, aliases };
1181
1301
  }
1182
1302
 
1183
1303
  // src/validation/schema.ts
@@ -8691,6 +8811,46 @@ function massAssign(options) {
8691
8811
  }
8692
8812
 
8693
8813
  // src/middleware/protect.ts
8814
+ function getClientIp(req) {
8815
+ const xff = req?.headers?.["x-forwarded-for"] ?? req?.headers?.["X-Forwarded-For"];
8816
+ if (typeof xff === "string" && xff.length > 0) {
8817
+ const first = xff.split(",")[0]?.trim();
8818
+ if (first) return first;
8819
+ }
8820
+ if (typeof req?.ip === "string") return req.ip;
8821
+ const remote = req?.socket?.remoteAddress;
8822
+ return typeof remote === "string" ? remote : "";
8823
+ }
8824
+ function correlationMiddleware(opts) {
8825
+ const vector = opts.vector ?? "request";
8826
+ const usernameField = opts.usernameField ?? "username";
8827
+ const statusCode = opts.statusCode ?? 429;
8828
+ const message = opts.message ?? "Suspicious request pattern detected.";
8829
+ return function(req, res, next) {
8830
+ const ip = getClientIp(req);
8831
+ if (!ip) return next();
8832
+ const route = opts.route ?? (req.path || req.url || "/");
8833
+ const username = req?.body?.[usernameField];
8834
+ const distinctValue = typeof username === "string" && username.length > 0 ? username : void 0;
8835
+ const detections = opts.window.record(
8836
+ ip,
8837
+ vector,
8838
+ route,
8839
+ req.method || "GET",
8840
+ distinctValue
8841
+ );
8842
+ if (detections.scanner || detections.credentialStuffing || detections.raceWindow) {
8843
+ res.status(statusCode).json({
8844
+ error: message,
8845
+ scanner: detections.scanner,
8846
+ credential_stuffing: detections.credentialStuffing,
8847
+ race_window: detections.raceWindow
8848
+ });
8849
+ return;
8850
+ }
8851
+ next();
8852
+ };
8853
+ }
8694
8854
  function resolve(override, defaults) {
8695
8855
  if (override === false) return null;
8696
8856
  if (override === void 0) return defaults;
@@ -8710,6 +8870,11 @@ function protectLogin(options = {}) {
8710
8870
  if (csrf) middlewares.push(csrfProtection(csrf));
8711
8871
  const sanitize = resolve(options.sanitize, {});
8712
8872
  if (sanitize) middlewares.push(createSanitizer(sanitize));
8873
+ if (options.correlation) {
8874
+ middlewares.push(
8875
+ correlationMiddleware({ vector: "login", ...options.correlation })
8876
+ );
8877
+ }
8713
8878
  return middlewares;
8714
8879
  }
8715
8880
  function protectSignup(options = {}) {
@@ -8726,6 +8891,11 @@ function protectSignup(options = {}) {
8726
8891
  if (sanitize) middlewares.push(createSanitizer(sanitize));
8727
8892
  const signup = resolve(options.signup, {});
8728
8893
  if (signup) middlewares.push(signupProtection(signup));
8894
+ if (options.correlation) {
8895
+ middlewares.push(
8896
+ correlationMiddleware({ vector: "signup", ...options.correlation })
8897
+ );
8898
+ }
8729
8899
  return middlewares;
8730
8900
  }
8731
8901
  function protectApi(options = {}) {
@@ -8736,6 +8906,11 @@ function protectApi(options = {}) {
8736
8906
  if (cors) middlewares.push(safeCors(cors));
8737
8907
  const sanitize = resolve(options.sanitize, {});
8738
8908
  if (sanitize) middlewares.push(createSanitizer(sanitize));
8909
+ if (options.correlation) {
8910
+ middlewares.push(
8911
+ correlationMiddleware({ vector: "api", ...options.correlation })
8912
+ );
8913
+ }
8739
8914
  return middlewares;
8740
8915
  }
8741
8916
 
@@ -8743,7 +8918,9 @@ function protectApi(options = {}) {
8743
8918
  var DEFAULT_MESSAGES = {
8744
8919
  depth: "Query exceeds maximum nesting depth",
8745
8920
  length: "Query exceeds maximum length",
8746
- introspection: "Introspection queries are disabled"
8921
+ introspection: "Introspection queries are disabled",
8922
+ aliases: "Query exceeds maximum alias count (alias-bomb protection)",
8923
+ fragment_cycle: "Query contains a cyclic fragment definition"
8747
8924
  };
8748
8925
  function extractQuery(req) {
8749
8926
  const bodyQuery = typeof req.body === "object" && req.body !== null ? req.body.query : void 0;
@@ -8882,6 +9059,187 @@ function responseSplittingGuard(options = {}) {
8882
9059
  };
8883
9060
  }
8884
9061
 
9062
+ // src/middleware/correlation.ts
9063
+ var EMPTY_DETECTIONS = Object.freeze({
9064
+ scanner: false,
9065
+ credentialStuffing: false,
9066
+ raceWindow: false,
9067
+ distinctVectors: 0,
9068
+ distinctValues: 0,
9069
+ requestsInWindow: 0
9070
+ });
9071
+ function normalizePair(a, b) {
9072
+ return a < b ? `${a}${b}` : `${b}${a}`;
9073
+ }
9074
+ var CorrelationWindow = class {
9075
+ constructor(options = {}) {
9076
+ // Map iteration order in JS is insertion order, so re-inserting on
9077
+ // access gives us LRU behaviour without a separate linked list.
9078
+ this.buckets = /* @__PURE__ */ new Map();
9079
+ const {
9080
+ windowSeconds = 60,
9081
+ maxIps = 1e4,
9082
+ maxEventsPerIp = 200,
9083
+ scannerDistinctVectors = 3,
9084
+ scannerMinRequests = 20,
9085
+ credentialStuffingDistinctValues = 10,
9086
+ raceWindowMs = 200,
9087
+ racePairs
9088
+ } = options;
9089
+ if (windowSeconds <= 0) throw new Error("windowSeconds must be > 0");
9090
+ if (maxIps < 1) throw new Error("maxIps must be >= 1");
9091
+ if (maxEventsPerIp < 1) throw new Error("maxEventsPerIp must be >= 1");
9092
+ this.windowSeconds = windowSeconds;
9093
+ this.maxIps = maxIps;
9094
+ this.maxEventsPerIp = maxEventsPerIp;
9095
+ this.scannerDistinctVectors = scannerDistinctVectors;
9096
+ this.scannerMinRequests = scannerMinRequests;
9097
+ this.csDistinctValues = credentialStuffingDistinctValues;
9098
+ this.raceWindowSeconds = raceWindowMs / 1e3;
9099
+ this.racePairKeys = /* @__PURE__ */ new Set();
9100
+ this.racePairTuples = [];
9101
+ if (racePairs) {
9102
+ for (const [a, b] of racePairs) {
9103
+ const key = normalizePair(a, b);
9104
+ if (!this.racePairKeys.has(key)) {
9105
+ this.racePairKeys.add(key);
9106
+ const sorted = a < b ? [a, b] : [b, a];
9107
+ this.racePairTuples.push(sorted);
9108
+ }
9109
+ }
9110
+ }
9111
+ }
9112
+ record(ip, vector, route, method = "GET", distinctValue, now) {
9113
+ if (!ip) return EMPTY_DETECTIONS;
9114
+ const ts = now ?? Date.now() / 1e3;
9115
+ const event = {
9116
+ timestamp: ts,
9117
+ vector,
9118
+ route,
9119
+ method,
9120
+ distinctValue
9121
+ };
9122
+ let bucket = this.buckets.get(ip);
9123
+ if (bucket === void 0) {
9124
+ bucket = { events: [] };
9125
+ this.buckets.set(ip, bucket);
9126
+ while (this.buckets.size > this.maxIps) {
9127
+ const oldest = this.buckets.keys().next().value;
9128
+ if (oldest === void 0) break;
9129
+ this.buckets.delete(oldest);
9130
+ }
9131
+ } else {
9132
+ this.buckets.delete(ip);
9133
+ this.buckets.set(ip, bucket);
9134
+ }
9135
+ bucket.events.push(event);
9136
+ this.evictStale(bucket, ts);
9137
+ return this.evaluate(bucket, route);
9138
+ }
9139
+ detectScanner(ip, now) {
9140
+ const bucket = this.buckets.get(ip);
9141
+ if (bucket === void 0) return false;
9142
+ this.evictStale(bucket, now ?? Date.now() / 1e3);
9143
+ return this.isScanner(bucket);
9144
+ }
9145
+ detectCredentialStuffing(ip, route, now) {
9146
+ const bucket = this.buckets.get(ip);
9147
+ if (bucket === void 0) return false;
9148
+ this.evictStale(bucket, now ?? Date.now() / 1e3);
9149
+ return this.isCredentialStuffing(bucket, route);
9150
+ }
9151
+ detectRaceWindow(ip, routePair, now) {
9152
+ const bucket = this.buckets.get(ip);
9153
+ if (bucket === void 0) return false;
9154
+ this.evictStale(bucket, now ?? Date.now() / 1e3);
9155
+ const sorted = routePair[0] < routePair[1] ? routePair : [routePair[1], routePair[0]];
9156
+ return this.racePairInBucket(bucket, sorted);
9157
+ }
9158
+ reset(ip) {
9159
+ if (ip === void 0) {
9160
+ this.buckets.clear();
9161
+ } else {
9162
+ this.buckets.delete(ip);
9163
+ }
9164
+ }
9165
+ stats() {
9166
+ let events = 0;
9167
+ for (const b of this.buckets.values()) events += b.events.length;
9168
+ return { trackedIps: this.buckets.size, eventsInWindow: events };
9169
+ }
9170
+ // -------------------------------------------------------- internals
9171
+ evictStale(bucket, now) {
9172
+ const cutoff = now - this.windowSeconds;
9173
+ let drop = 0;
9174
+ while (drop < bucket.events.length && bucket.events[drop].timestamp < cutoff) {
9175
+ drop++;
9176
+ }
9177
+ if (drop > 0) bucket.events.splice(0, drop);
9178
+ if (bucket.events.length > this.maxEventsPerIp) {
9179
+ bucket.events.splice(0, bucket.events.length - this.maxEventsPerIp);
9180
+ }
9181
+ }
9182
+ evaluate(bucket, route) {
9183
+ const vectors = /* @__PURE__ */ new Set();
9184
+ const values = /* @__PURE__ */ new Set();
9185
+ for (const e of bucket.events) {
9186
+ vectors.add(e.vector);
9187
+ if (e.route === route && e.distinctValue !== void 0) {
9188
+ values.add(e.distinctValue);
9189
+ }
9190
+ }
9191
+ return {
9192
+ scanner: this.isScanner(bucket),
9193
+ credentialStuffing: this.isCredentialStuffing(bucket, route),
9194
+ raceWindow: this.isRaceAny(bucket),
9195
+ distinctVectors: vectors.size,
9196
+ distinctValues: values.size,
9197
+ requestsInWindow: bucket.events.length
9198
+ };
9199
+ }
9200
+ isScanner(bucket) {
9201
+ if (bucket.events.length < this.scannerMinRequests) return false;
9202
+ const vectors = /* @__PURE__ */ new Set();
9203
+ for (const e of bucket.events) vectors.add(e.vector);
9204
+ return vectors.size >= this.scannerDistinctVectors;
9205
+ }
9206
+ isCredentialStuffing(bucket, route) {
9207
+ const values = /* @__PURE__ */ new Set();
9208
+ for (const e of bucket.events) {
9209
+ if (e.route === route && e.distinctValue !== void 0) {
9210
+ values.add(e.distinctValue);
9211
+ }
9212
+ }
9213
+ return values.size >= this.csDistinctValues;
9214
+ }
9215
+ racePairInBucket(bucket, sorted) {
9216
+ const [a, b] = sorted;
9217
+ const aTs = [];
9218
+ const bTs = [];
9219
+ for (const e of bucket.events) {
9220
+ if (e.route === a) aTs.push(e.timestamp);
9221
+ else if (e.route === b) bTs.push(e.timestamp);
9222
+ }
9223
+ if (aTs.length === 0 || bTs.length === 0) return false;
9224
+ let ai = 0;
9225
+ let bi = 0;
9226
+ while (ai < aTs.length && bi < bTs.length) {
9227
+ const diff = aTs[ai] - bTs[bi];
9228
+ if (Math.abs(diff) <= this.raceWindowSeconds) return true;
9229
+ if (diff < 0) ai++;
9230
+ else bi++;
9231
+ }
9232
+ return false;
9233
+ }
9234
+ isRaceAny(bucket) {
9235
+ for (const pair of this.racePairTuples) {
9236
+ if (this.racePairInBucket(bucket, pair)) return true;
9237
+ }
9238
+ return false;
9239
+ }
9240
+ };
9241
+
9242
+ exports.CorrelationWindow = CorrelationWindow;
8885
9243
  exports.ResponseSplittingError = ResponseSplittingError;
8886
9244
  exports.arcis = arcis;
8887
9245
  exports.arcisFunction = arcisWithMethods;