guardian-risk 0.2.0 → 0.3.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.
package/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var crypto = require('crypto');
4
+ var net = require('net');
4
5
 
5
6
  // src/constants/defaults.ts
6
7
  var DEFAULT_RISK_LEVELS = [
@@ -10,6 +11,44 @@ var DEFAULT_RISK_LEVELS = [
10
11
  { max: Infinity, level: "CRITICAL" }
11
12
  ];
12
13
 
14
+ // src/constants/security.ts
15
+ var MAX_RULES = 1e3;
16
+ var MAX_SIGNALS = 1e3;
17
+ var MAX_KEY_LENGTH = 256;
18
+ var MAX_RULE_SCORE = 1e4;
19
+ var MAX_TOTAL_SCORE = 1e6;
20
+ var MAX_SIGNAL_STRING_LENGTH = 4096;
21
+ var MAX_SESSION_ID_LENGTH = 128;
22
+ var SESSION_ID_PATTERN = /^[a-zA-Z0-9._-]+$/;
23
+ var HOOK_TIMEOUT_MS = 1e4;
24
+ var BLOCKED_SIGNAL_KEYS = /* @__PURE__ */ new Set([
25
+ "__proto__",
26
+ "constructor",
27
+ "prototype"
28
+ ]);
29
+
30
+ // src/hooks/runHooks.ts
31
+ async function runHooks(hooks, context, timeoutMs = HOOK_TIMEOUT_MS) {
32
+ for (const hook of hooks) {
33
+ await runWithTimeout(() => hook(context), timeoutMs);
34
+ }
35
+ }
36
+ async function runWithTimeout(operation, timeoutMs) {
37
+ let timer;
38
+ const timeout = new Promise((_, reject) => {
39
+ timer = setTimeout(() => {
40
+ reject(new Error(`Guardian analyze hook timed out after ${timeoutMs}ms`));
41
+ }, timeoutMs);
42
+ });
43
+ try {
44
+ await Promise.race([Promise.resolve(operation()), timeout]);
45
+ } finally {
46
+ if (timer !== void 0) {
47
+ clearTimeout(timer);
48
+ }
49
+ }
50
+ }
51
+
13
52
  // src/utils/resolveLevel.ts
14
53
  function resolveLevel(score, thresholds = DEFAULT_RISK_LEVELS) {
15
54
  for (const threshold of thresholds) {
@@ -29,11 +68,14 @@ var ReportBuilder = class {
29
68
  build(score, matchedRules, thresholds, analyzedAt = (/* @__PURE__ */ new Date()).toISOString()) {
30
69
  const reasons = matchedRules.map((rule) => rule.reason);
31
70
  const level = resolveLevel(score, thresholds);
71
+ const frozenRules = matchedRules.map(
72
+ (rule) => Object.freeze({ ...rule })
73
+ );
32
74
  const report = {
33
75
  score,
34
76
  level,
35
77
  reasons: Object.freeze([...reasons]),
36
- matchedRules: Object.freeze([...matchedRules]),
78
+ matchedRules: Object.freeze(frozenRules),
37
79
  analyzedAt
38
80
  };
39
81
  return Object.freeze(report);
@@ -49,12 +91,19 @@ var RuleEvaluator = class {
49
91
  evaluate(rules, signals) {
50
92
  const matched = [];
51
93
  for (const rule of rules) {
52
- if (rule.when(signals)) {
94
+ let isMatched = false;
95
+ try {
96
+ isMatched = rule.when(signals);
97
+ } catch {
98
+ isMatched = false;
99
+ }
100
+ if (isMatched) {
53
101
  matched.push({
54
102
  id: rule.id,
55
103
  name: rule.name,
56
104
  score: rule.score,
57
- reason: rule.reason ?? rule.name
105
+ reason: rule.reason ?? rule.name,
106
+ ...rule.group !== void 0 ? { group: rule.group } : {}
58
107
  });
59
108
  }
60
109
  }
@@ -65,10 +114,34 @@ var RuleEvaluator = class {
65
114
  // src/score/ScoreCalculator.ts
66
115
  var ScoreCalculator = class {
67
116
  /**
68
- * Sum scores from all matched rules.
117
+ * Sum scores from matched rules, applying group caps when configured.
69
118
  */
70
- calculate(matchedRules) {
71
- return matchedRules.reduce((total, rule) => total + rule.score, 0);
119
+ calculate(matchedRules, groupCaps = []) {
120
+ const caps = new Map(groupCaps.map((cap) => [cap.name, cap.maxScore]));
121
+ const grouped = /* @__PURE__ */ new Map();
122
+ let ungroupedTotal = 0;
123
+ for (const rule of matchedRules) {
124
+ if (rule.group !== void 0) {
125
+ grouped.set(rule.group, (grouped.get(rule.group) ?? 0) + rule.score);
126
+ } else {
127
+ ungroupedTotal += rule.score;
128
+ }
129
+ }
130
+ let total = ungroupedTotal;
131
+ for (const [groupName, groupScore] of grouped) {
132
+ const cap = caps.get(groupName);
133
+ total += cap !== void 0 ? Math.min(groupScore, cap) : groupScore;
134
+ }
135
+ if (!Number.isFinite(total)) {
136
+ return 0;
137
+ }
138
+ if (total > MAX_TOTAL_SCORE) {
139
+ return MAX_TOTAL_SCORE;
140
+ }
141
+ if (total < -MAX_TOTAL_SCORE) {
142
+ return -MAX_TOTAL_SCORE;
143
+ }
144
+ return total;
72
145
  }
73
146
  };
74
147
  function validateSignalValue(value) {
@@ -76,7 +149,105 @@ function validateSignalValue(value) {
76
149
  return true;
77
150
  }
78
151
  const type = typeof value;
79
- return type === "string" || type === "number" || type === "boolean";
152
+ if (type === "string") {
153
+ return value.length <= MAX_SIGNAL_STRING_LENGTH;
154
+ }
155
+ if (type === "number") {
156
+ return Number.isFinite(value);
157
+ }
158
+ return type === "boolean";
159
+ }
160
+ function validateSignalKey(key) {
161
+ if (typeof key !== "string" || key.length === 0) {
162
+ throw new TypeError("Signal key must be a non-empty string");
163
+ }
164
+ if (key.length > MAX_KEY_LENGTH) {
165
+ throw new TypeError(`Signal key exceeds maximum length of ${MAX_KEY_LENGTH}`);
166
+ }
167
+ if (BLOCKED_SIGNAL_KEYS.has(key)) {
168
+ throw new TypeError(`Signal key "${key}" is not allowed`);
169
+ }
170
+ }
171
+ function validateRuleInput(input) {
172
+ if (typeof input.name !== "string" || input.name.trim().length === 0) {
173
+ throw new TypeError("Rule name must be a non-empty string");
174
+ }
175
+ if (input.name.length > MAX_KEY_LENGTH) {
176
+ throw new TypeError(`Rule name exceeds maximum length of ${MAX_KEY_LENGTH}`);
177
+ }
178
+ if (typeof input.when !== "function") {
179
+ throw new TypeError("Rule when must be a function");
180
+ }
181
+ if (typeof input.score !== "number" || !Number.isFinite(input.score)) {
182
+ throw new TypeError("Rule score must be a finite number");
183
+ }
184
+ if (Math.abs(input.score) > MAX_RULE_SCORE) {
185
+ throw new TypeError(`Rule score must be between -${MAX_RULE_SCORE} and ${MAX_RULE_SCORE}`);
186
+ }
187
+ if (input.reason !== void 0) {
188
+ if (typeof input.reason !== "string") {
189
+ throw new TypeError("Rule reason must be a string");
190
+ }
191
+ if (input.reason.length > MAX_KEY_LENGTH) {
192
+ throw new TypeError(`Rule reason exceeds maximum length of ${MAX_KEY_LENGTH}`);
193
+ }
194
+ }
195
+ if (input.description !== void 0) {
196
+ if (typeof input.description !== "string") {
197
+ throw new TypeError("Rule description must be a string");
198
+ }
199
+ if (input.description.length > MAX_KEY_LENGTH) {
200
+ throw new TypeError(`Rule description exceeds maximum length of ${MAX_KEY_LENGTH}`);
201
+ }
202
+ }
203
+ if (input.group !== void 0) {
204
+ if (typeof input.group !== "string" || input.group.trim().length === 0) {
205
+ throw new TypeError("Rule group must be a non-empty string");
206
+ }
207
+ if (input.group.length > MAX_KEY_LENGTH) {
208
+ throw new TypeError(`Rule group exceeds maximum length of ${MAX_KEY_LENGTH}`);
209
+ }
210
+ }
211
+ }
212
+ function validatePlugin(plugin) {
213
+ if (typeof plugin.name !== "string" || plugin.name.trim().length === 0) {
214
+ throw new TypeError("Plugin name must be a non-empty string");
215
+ }
216
+ if (plugin.name.length > MAX_KEY_LENGTH) {
217
+ throw new TypeError(`Plugin name exceeds maximum length of ${MAX_KEY_LENGTH}`);
218
+ }
219
+ if (typeof plugin.install !== "function") {
220
+ throw new TypeError("Plugin install must be a function");
221
+ }
222
+ }
223
+ function validateRiskLevels(levels) {
224
+ if (levels.length === 0) {
225
+ throw new TypeError("Risk levels must contain at least one threshold");
226
+ }
227
+ for (const threshold of levels) {
228
+ if (typeof threshold.level !== "string" || threshold.level.trim().length === 0) {
229
+ throw new TypeError("Risk level label must be a non-empty string");
230
+ }
231
+ if (typeof threshold.max !== "number" || !Number.isFinite(threshold.max) && threshold.max !== Infinity) {
232
+ throw new TypeError("Risk level max must be a finite number or Infinity");
233
+ }
234
+ }
235
+ }
236
+ function validateRuleGroupInput(input) {
237
+ if (typeof input.name !== "string" || input.name.trim().length === 0) {
238
+ throw new TypeError("Rule group name must be a non-empty string");
239
+ }
240
+ if (input.name.length > MAX_KEY_LENGTH) {
241
+ throw new TypeError(`Rule group name exceeds maximum length of ${MAX_KEY_LENGTH}`);
242
+ }
243
+ if (input.maxScore !== void 0) {
244
+ if (typeof input.maxScore !== "number" || !Number.isFinite(input.maxScore) || input.maxScore < 0) {
245
+ throw new TypeError("Rule group maxScore must be a non-negative finite number");
246
+ }
247
+ }
248
+ if (!Array.isArray(input.rules) || input.rules.length === 0) {
249
+ throw new TypeError("Rule group must contain at least one rule");
250
+ }
80
251
  }
81
252
  function generateId() {
82
253
  return crypto.randomUUID();
@@ -89,11 +260,15 @@ var SignalStore = class {
89
260
  * Set a signal value. Overwrites any existing value for the key.
90
261
  */
91
262
  set(key, value) {
263
+ validateSignalKey(key);
92
264
  if (!validateSignalValue(value)) {
93
265
  throw new TypeError(
94
266
  `Invalid signal value for "${key}": signals must be string, number, boolean, or null`
95
267
  );
96
268
  }
269
+ if (!this.signals.has(key) && this.signals.size >= MAX_SIGNALS) {
270
+ throw new RangeError(`Cannot exceed maximum of ${MAX_SIGNALS} signals`);
271
+ }
97
272
  this.signals.set(key, value);
98
273
  return this;
99
274
  }
@@ -113,7 +288,7 @@ var SignalStore = class {
113
288
  * Returns a frozen snapshot of all signals.
114
289
  */
115
290
  getAll() {
116
- const snapshot = {};
291
+ const snapshot = /* @__PURE__ */ Object.create(null);
117
292
  for (const [key, value] of this.signals) {
118
293
  snapshot[key] = value;
119
294
  }
@@ -135,11 +310,15 @@ var RiskEngine = class {
135
310
  }
136
311
  deps;
137
312
  rules = [];
313
+ groupCaps = [];
138
314
  thresholds;
139
315
  /**
140
316
  * Register a rule for evaluation.
141
317
  */
142
318
  addRule(rule) {
319
+ if (this.rules.length >= MAX_RULES) {
320
+ throw new RangeError(`Cannot exceed maximum of ${MAX_RULES} rules`);
321
+ }
143
322
  this.rules.push(rule);
144
323
  }
145
324
  /**
@@ -148,13 +327,30 @@ var RiskEngine = class {
148
327
  getRules() {
149
328
  return this.rules;
150
329
  }
330
+ /**
331
+ * Get configured per-group score caps.
332
+ */
333
+ getGroupCaps() {
334
+ return this.groupCaps;
335
+ }
336
+ /**
337
+ * Cap the combined score of matched rules in a group.
338
+ */
339
+ setGroupCap(name, maxScore) {
340
+ const existing = this.groupCaps.findIndex((cap) => cap.name === name);
341
+ if (existing >= 0) {
342
+ this.groupCaps[existing] = { name, maxScore };
343
+ return;
344
+ }
345
+ this.groupCaps.push({ name, maxScore });
346
+ }
151
347
  /**
152
348
  * Run the full risk analysis pipeline.
153
349
  */
154
350
  analyze() {
155
351
  const signals = this.deps.signalStore.getAll();
156
352
  const matchedRules = this.deps.ruleEvaluator.evaluate(this.rules, signals);
157
- const score = this.deps.scoreCalculator.calculate(matchedRules);
353
+ const score = this.deps.scoreCalculator.calculate(matchedRules, this.groupCaps);
158
354
  return this.deps.reportBuilder.build(score, matchedRules, this.thresholds);
159
355
  }
160
356
  };
@@ -165,19 +361,28 @@ var RuleBuilder = class {
165
361
  * Create a new rule from input configuration.
166
362
  */
167
363
  static create(input) {
364
+ validateRuleInput(input);
168
365
  const rule = {
169
366
  id: generateId(),
170
367
  name: input.name,
171
368
  score: input.score,
172
369
  when: input.when,
173
370
  ...input.description !== void 0 ? { description: input.description } : {},
174
- ...input.reason !== void 0 ? { reason: input.reason } : {}
371
+ ...input.reason !== void 0 ? { reason: input.reason } : {},
372
+ ...input.group !== void 0 ? { group: input.group } : {}
175
373
  };
176
374
  return rule;
177
375
  }
178
376
  };
179
377
 
180
378
  // src/plugins/PluginRegistry.ts
379
+ var PluginInstallError = class extends Error {
380
+ constructor(pluginName, cause) {
381
+ const message = cause instanceof Error ? cause.message : "Unknown plugin install error";
382
+ super(`Plugin "${pluginName}" failed to install: ${message}`);
383
+ this.name = "PluginInstallError";
384
+ }
385
+ };
181
386
  var PluginAlreadyInstalledError = class extends Error {
182
387
  constructor(pluginName) {
183
388
  super(`Plugin "${pluginName}" is already installed`);
@@ -191,10 +396,15 @@ var PluginRegistry = class {
191
396
  * Each plugin name may only be installed once per Guardian instance.
192
397
  */
193
398
  install(plugin, guardian) {
399
+ validatePlugin(plugin);
194
400
  if (this.installed.has(plugin.name)) {
195
401
  throw new PluginAlreadyInstalledError(plugin.name);
196
402
  }
197
- plugin.install(guardian);
403
+ try {
404
+ plugin.install(guardian);
405
+ } catch (error) {
406
+ throw new PluginInstallError(plugin.name, error);
407
+ }
198
408
  this.installed.add(plugin.name);
199
409
  }
200
410
  /**
@@ -203,6 +413,15 @@ var PluginRegistry = class {
203
413
  has(name) {
204
414
  return this.installed.has(name);
205
415
  }
416
+ /**
417
+ * Copy installed plugin names from a template (used by Guardian.fork).
418
+ * Does not call install() — rules and hooks are copied separately.
419
+ */
420
+ adoptInstalled(names) {
421
+ for (const name of names) {
422
+ this.installed.add(name);
423
+ }
424
+ }
206
425
  /**
207
426
  * Get names of all installed plugins in registration order.
208
427
  */
@@ -212,12 +431,20 @@ var PluginRegistry = class {
212
431
  };
213
432
 
214
433
  // src/engine/Guardian.ts
215
- var Guardian = class {
434
+ var Guardian = class _Guardian {
216
435
  signalStore;
217
436
  riskEngine;
218
437
  pluginRegistry = new PluginRegistry();
438
+ plugins = [];
439
+ beforeHooks = [];
440
+ afterHooks = [];
441
+ thresholds;
442
+ analyzing = false;
219
443
  constructor(config = {}) {
220
- const thresholds = config.levels ?? DEFAULT_RISK_LEVELS;
444
+ this.thresholds = config.levels !== void 0 ? [...config.levels] : DEFAULT_RISK_LEVELS;
445
+ if (config.levels !== void 0) {
446
+ validateRiskLevels(config.levels);
447
+ }
221
448
  this.signalStore = new SignalStore();
222
449
  const deps = {
223
450
  signalStore: this.signalStore,
@@ -225,7 +452,7 @@ var Guardian = class {
225
452
  scoreCalculator: new ScoreCalculator(),
226
453
  reportBuilder: new ReportBuilder()
227
454
  };
228
- this.riskEngine = new RiskEngine(deps, thresholds);
455
+ this.riskEngine = new RiskEngine(deps, this.thresholds);
229
456
  }
230
457
  /**
231
458
  * Add a signal value for risk evaluation.
@@ -234,21 +461,60 @@ var Guardian = class {
234
461
  this.signalStore.set(key, value);
235
462
  return this;
236
463
  }
464
+ /**
465
+ * Read a signal value without modifying state.
466
+ */
467
+ getSignal(key) {
468
+ return this.signalStore.get(key);
469
+ }
237
470
  /**
238
471
  * Register a rule. ID is auto-generated.
239
472
  */
240
473
  rule(input) {
474
+ this.assertNotAnalyzing("register rules");
241
475
  const rule = RuleBuilder.create(input);
242
476
  this.riskEngine.addRule(rule);
243
477
  return this;
244
478
  }
479
+ /**
480
+ * Register a named group of rules with an optional combined score cap.
481
+ */
482
+ ruleGroup(input) {
483
+ this.assertNotAnalyzing("register rule groups");
484
+ validateRuleGroupInput(input);
485
+ for (const ruleInput of input.rules) {
486
+ this.rule({ ...ruleInput, group: input.name });
487
+ }
488
+ if (input.maxScore !== void 0) {
489
+ this.riskEngine.setGroupCap(input.name, input.maxScore);
490
+ }
491
+ return this;
492
+ }
245
493
  /**
246
494
  * Install a plugin. Each plugin name may only be registered once.
247
495
  */
248
496
  use(plugin) {
497
+ this.assertNotAnalyzing("install plugins");
498
+ this.plugins.push(plugin);
249
499
  this.pluginRegistry.install(plugin, this);
250
500
  return this;
251
501
  }
502
+ /**
503
+ * Register a hook that runs before rule evaluation.
504
+ * Use for loading signals from requests, Redis, IP lookups, etc.
505
+ */
506
+ beforeAnalyze(hook) {
507
+ this.beforeHooks.push(hook);
508
+ return this;
509
+ }
510
+ /**
511
+ * Register a hook that runs after the report is built.
512
+ * Use for audit logging, metrics, or blocking responses.
513
+ */
514
+ afterAnalyze(hook) {
515
+ this.afterHooks.push(hook);
516
+ return this;
517
+ }
252
518
  /**
253
519
  * Returns names of installed plugins.
254
520
  */
@@ -256,29 +522,204 @@ var Guardian = class {
256
522
  return this.pluginRegistry.getInstalled();
257
523
  }
258
524
  /**
259
- * Run risk analysis and return an immutable report.
525
+ * Run risk analysis synchronously (skips lifecycle hooks).
526
+ * Prefer {@link analyzeAsync} when hooks are registered.
260
527
  */
261
528
  analyze() {
529
+ if (this.beforeHooks.length > 0 || this.afterHooks.length > 0) {
530
+ throw new Error(
531
+ "Guardian has analyze hooks registered. Use analyzeAsync() instead of analyze()."
532
+ );
533
+ }
262
534
  return this.riskEngine.analyze();
263
535
  }
264
536
  /**
265
- * Clear all signals. Rules and installed plugins persist across resets.
537
+ * Run lifecycle hooks, evaluate rules, and return an immutable report.
538
+ *
539
+ * @param context Optional caller context passed to hooks (e.g. Express `req`).
540
+ */
541
+ async analyzeAsync(context) {
542
+ this.analyzing = true;
543
+ try {
544
+ const analyzeContext = {
545
+ data: context,
546
+ guardian: this
547
+ };
548
+ await runHooks(this.beforeHooks, analyzeContext);
549
+ const report = this.riskEngine.analyze();
550
+ const afterContext = {
551
+ ...analyzeContext,
552
+ report
553
+ };
554
+ await runHooks(this.afterHooks, afterContext);
555
+ return report;
556
+ } finally {
557
+ this.analyzing = false;
558
+ }
559
+ }
560
+ /**
561
+ * Create an isolated copy sharing rules, plugins, and hooks.
562
+ * Each fork has its own signal store for safe concurrent use.
563
+ */
564
+ fork() {
565
+ const child = new _Guardian({ levels: [...this.thresholds] });
566
+ for (const rule of this.riskEngine.getRules()) {
567
+ child.riskEngine.addRule(rule);
568
+ }
569
+ for (const cap of this.riskEngine.getGroupCaps()) {
570
+ child.riskEngine.setGroupCap(cap.name, cap.maxScore);
571
+ }
572
+ child.plugins.push(...this.plugins);
573
+ child.pluginRegistry.adoptInstalled(this.pluginRegistry.getInstalled());
574
+ for (const hook of this.beforeHooks) {
575
+ child.beforeHooks.push(hook);
576
+ }
577
+ for (const hook of this.afterHooks) {
578
+ child.afterHooks.push(hook);
579
+ }
580
+ return child;
581
+ }
582
+ /**
583
+ * Clear all signals. Rules, plugins, and hooks persist across resets.
266
584
  */
267
585
  reset() {
268
586
  this.signalStore.clear();
269
587
  return this;
270
588
  }
589
+ assertNotAnalyzing(action) {
590
+ if (this.analyzing) {
591
+ throw new Error(`Cannot ${action} while analysis is in progress`);
592
+ }
593
+ }
271
594
  };
272
595
 
596
+ // src/guardian/defineSignals.ts
597
+ function defineSignals() {
598
+ return {
599
+ create(config) {
600
+ return new Guardian(config);
601
+ }
602
+ };
603
+ }
604
+
605
+ // src/presets/applyRules.ts
606
+ function applyRules(guardian, rules) {
607
+ for (const rule of rules) {
608
+ guardian.rule(rule);
609
+ }
610
+ return guardian;
611
+ }
612
+
613
+ // src/presets/botDetection.ts
614
+ var botDetectionRules = [
615
+ {
616
+ name: "LinearMouseMovement",
617
+ reason: "Mouse movement is unnaturally linear",
618
+ when: (s) => s.mouseLinearity > 0.9,
619
+ score: 25
620
+ },
621
+ {
622
+ name: "RequestBurst",
623
+ reason: "Unusually high request rate in short window",
624
+ when: (s) => s.requestBurst > 50,
625
+ score: 30
626
+ },
627
+ {
628
+ name: "HeadlessBrowser",
629
+ reason: "User agent indicates headless browser",
630
+ when: (s) => s.headlessUA === true,
631
+ score: 40
632
+ },
633
+ {
634
+ name: "NewSession",
635
+ reason: "Session is very new",
636
+ when: (s) => s.sessionAgeSeconds < 10,
637
+ score: 10
638
+ },
639
+ {
640
+ name: "HighRequestRate",
641
+ reason: "Too many requests per minute",
642
+ when: (s) => s.requestsPerMinute > 30,
643
+ score: 35
644
+ }
645
+ ];
646
+ var loginProtectionRules = [
647
+ {
648
+ name: "BruteForceAttempts",
649
+ reason: "Multiple failed login attempts",
650
+ when: (s) => s.loginAttempts > 5,
651
+ score: 45,
652
+ group: "login"
653
+ },
654
+ {
655
+ name: "RapidLoginBurst",
656
+ reason: "Login attempts in rapid succession",
657
+ when: (s) => s.requestsPerMinute > 10,
658
+ score: 25,
659
+ group: "login"
660
+ }
661
+ ];
662
+ function parseIpAddress(value) {
663
+ const trimmed = value.trim();
664
+ if (trimmed.length === 0 || trimmed.length > 45) {
665
+ return null;
666
+ }
667
+ return net.isIP(trimmed) === 0 ? null : trimmed;
668
+ }
669
+ function isPrivateIp(ip) {
670
+ const parsed = parseIpAddress(ip);
671
+ if (!parsed) {
672
+ return true;
673
+ }
674
+ if (net.isIP(parsed) === 4) {
675
+ if (parsed.startsWith("10.") || parsed.startsWith("127.") || parsed.startsWith("192.168.") || parsed.startsWith("169.254.") || parsed.startsWith("0.")) {
676
+ return true;
677
+ }
678
+ if (parsed.startsWith("172.")) {
679
+ const second = Number(parsed.split(".")[1]);
680
+ if (second >= 16 && second <= 31) {
681
+ return true;
682
+ }
683
+ }
684
+ if (parsed.startsWith("100.")) {
685
+ const second = Number(parsed.split(".")[1]);
686
+ if (second >= 64 && second <= 127) {
687
+ return true;
688
+ }
689
+ }
690
+ return false;
691
+ }
692
+ const lower = parsed.toLowerCase();
693
+ return lower === "::1" || lower.startsWith("fc") || lower.startsWith("fd") || lower.startsWith("fe80");
694
+ }
695
+ function sanitizeSessionId(value) {
696
+ const trimmed = value.trim();
697
+ if (trimmed.length === 0 || trimmed.length > MAX_SESSION_ID_LENGTH) {
698
+ return null;
699
+ }
700
+ if (!SESSION_ID_PATTERN.test(trimmed)) {
701
+ return null;
702
+ }
703
+ return trimmed;
704
+ }
705
+
273
706
  exports.DEFAULT_RISK_LEVELS = DEFAULT_RISK_LEVELS;
274
707
  exports.Guardian = Guardian;
708
+ exports.HOOK_TIMEOUT_MS = HOOK_TIMEOUT_MS;
709
+ exports.MAX_SIGNAL_STRING_LENGTH = MAX_SIGNAL_STRING_LENGTH;
275
710
  exports.PluginAlreadyInstalledError = PluginAlreadyInstalledError;
711
+ exports.PluginInstallError = PluginInstallError;
276
712
  exports.PluginRegistry = PluginRegistry;
277
713
  exports.RiskEngine = RiskEngine;
278
714
  exports.RuleBuilder = RuleBuilder;
279
715
  exports.RuleEvaluator = RuleEvaluator;
280
716
  exports.ScoreCalculator = ScoreCalculator;
281
717
  exports.SignalStore = SignalStore;
718
+ exports.applyRules = applyRules;
719
+ exports.botDetectionRules = botDetectionRules;
720
+ exports.defineSignals = defineSignals;
721
+ exports.isPrivateIp = isPrivateIp;
722
+ exports.loginProtectionRules = loginProtectionRules;
723
+ exports.parseIpAddress = parseIpAddress;
282
724
  exports.resolveLevel = resolveLevel;
283
- //# sourceMappingURL=index.cjs.map
284
- //# sourceMappingURL=index.cjs.map
725
+ exports.sanitizeSessionId = sanitizeSessionId;