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