@anonympins/fingerprint 0.7.2 → 0.7.4

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 (41) hide show
  1. package/CHANGELOG.md +36 -9
  2. package/README.md +1 -1
  3. package/package.json +109 -107
  4. package/public/fingerprint-anti-bot.zip +0 -0
  5. package/src/js/fingerprint.client.js +252 -45
  6. package/src/js/fingerprint.client.obfuscated.js +1 -1
  7. package/src/js/fingerprint.js +265 -185
  8. package/src/js/fingerprint.utils.js +32 -5
  9. package/src/js/fingerprint.worker.js +81 -0
  10. package/src/js/library.js +6 -2
  11. package/src/js/pow.solver.js +2 -3
  12. package/src/js/problem-manager.js +113 -113
  13. package/src/js/tests/fingerprint.client.init.test.js +30 -0
  14. package/src/js/tests/fingerprint.test.js +136 -4
  15. package/src/js/tests/threatIntel.test.js +76 -0
  16. package/src/php/AutoTuner.php +36 -18
  17. package/src/php/Challenge/ChallengeUtils.php +121 -240
  18. package/src/php/DirectFingerprint.php +16 -4
  19. package/src/php/FingerprintClient.php +17 -19
  20. package/src/php/FingerprintEngine.php +77 -39
  21. package/src/php/Ja3AnomalyDetector.php +20 -12
  22. package/src/php/Optimization/Optimization.php +5 -1
  23. package/src/php/Optimization/OptimizationOperators.php +5 -1
  24. package/src/php/Optimization/ProblemInitializers.php +16 -4
  25. package/src/php/ProblemManager.php +20 -14
  26. package/src/php/Tests/ChallengeUtilsTest.php +469 -453
  27. package/src/php/Tests/FingerprintEngineTest.php +6 -1
  28. package/src/php/Tests/RequestUtilsTest.php +1 -1
  29. package/src/php/Tests/ThreatIntelTest.php +64 -0
  30. package/src/php/Utils/Env.php +51 -49
  31. package/src/php/Utils/MaliciousPatterns.php +74 -74
  32. package/src/php/Utils/RequestUtils.php +231 -132
  33. package/src/php/WordPress/WpDbStore.php +4 -0
  34. package/src/php/WordPress/{fingerprint-wordpress.php → fingerprint-anti-bot.php} +131 -126
  35. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
  36. package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +1 -1
  37. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
  38. package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +1 -1
  39. package/src/php/WordPress/package.php +6 -3
  40. package/src/php/WordPress/readme.txt +36 -0
  41. package/public/fingerprint-wordpress.zip +0 -0
@@ -906,8 +906,76 @@ describe('Fingerprint & PoW Security Suite', () => {
906
906
  expect(req.fingerprint.score).toBeGreaterThanOrEqual(blockThreshold);
907
907
  });
908
908
 
909
- test('should redirect after a valid COMBINED (CPU+Mem) PoW solution is provided', async (context) => {
909
+ test('should redirect after a valid COMBINED (CPU+Mem) PoW solution is provided', async () => {
910
+ const ip = '127.0.0.1';
911
+ const originalPath = '/protected/combined';
912
+ const solverFingerprint = 'fp-combined-solution';
913
+ const userAgent = 'test-ua-combined';
914
+
915
+ vi.spyOn(__internal, 'getSuspicionVector').mockResolvedValue({
916
+ historyScore: 35,
917
+ });
918
+
919
+ const securityConfigWithLowDiff = {
920
+ ...securityConfig,
921
+ cpu: { minDifficultyBits: 4, ...securityConfig.cpu },
922
+ };
923
+ const middleware = powMiddleware(securityConfigWithLowDiff);
924
+
925
+ // 1. Déclencher le challenge
926
+ const req1 = {
927
+ path: originalPath, ip, cookies: {}, query: {},
928
+ headers: { 'user-agent': userAgent, 'x-device-fingerprint': solverFingerprint },
929
+ rawHeaders: ['User-Agent', userAgent], httpVersion: '1.1'
930
+ };
931
+ let challengeBody;
932
+ const res1 = {
933
+ status: () => res1,
934
+ send: (body) => { challengeBody = body; },
935
+ cookie: vi.fn()
936
+ };
937
+ req1.fingerprint = {};
938
+ await middleware(req1, res1, vi.fn());
939
+
940
+ expect(challengeBody).toContain('Enhanced Verification');
941
+
942
+ // 2. Résoudre le challenge
943
+ const nonce = challengeBody.match(/const nonce = "([^"]+)"/)[1];
944
+ const clientSecret = challengeBody.match(/const clientSecret = "([^"]+)"/)[1];
945
+ const cpuTargetHex = challengeBody.match(/const cpuTarget = BigInt\("0x" \+ "([^"]+)"\);/)[1];
946
+ const memDifficulty = parseInt(challengeBody.match(/const memDifficulty = (\d+)/)[1], 10);
947
+
948
+ const baseBlock = new TextEncoder().encode(`${nonce}:${clientSecret}:${solverFingerprint}:${ip}::`);
949
+ const cpuSolution = await solveCpuTargetInline(baseBlock, cpuTargetHex, () => {});
950
+ const memSolution = await solveMemory(`:${nonce}:${clientSecret}`, memDifficulty);
951
+
952
+ // 3. Soumettre la solution
953
+ const req2 = {
954
+ path: originalPath, ip, cookies: {},
955
+ query: {
956
+ pow_type: 'cpu_mem',
957
+ pow_nonce: nonce,
958
+ pow_solution_cpu: String(cpuSolution),
959
+ pow_solution_mem: JSON.stringify(memSolution),
960
+ pow_fp: solverFingerprint
961
+ },
962
+ headers: { 'user-agent': userAgent, 'x-device-fingerprint': solverFingerprint },
963
+ rawHeaders: ['User-Agent', userAgent], httpVersion: '1.1'
964
+ };
965
+ let capturedCookie;
966
+ const res2 = {
967
+ cookie: (name, value, options) => { capturedCookie = { name, value, options }; },
968
+ redirect: vi.fn(),
969
+ status: vi.fn(() => res2),
970
+ send: vi.fn(),
971
+ };
972
+
973
+ req2.fingerprint = {};
974
+ await middleware(req2, res2, vi.fn());
910
975
 
976
+ expect(res2.redirect).toHaveBeenCalledWith(originalPath);
977
+ expect(capturedCookie).toBeDefined();
978
+ expect(capturedCookie.name).toBe('pow_clearance');
911
979
  });
912
980
  });
913
981
 
@@ -1553,6 +1621,18 @@ describe('Fingerprint & PoW Security Suite', () => {
1553
1621
  expect(behaviorScore).toBe(0);
1554
1622
  });
1555
1623
 
1624
+ it('should return a score of 0 when x-behavior-metrics is 1 (human verified client-side)', () => {
1625
+ const context = { headers: { 'x-behavior-metrics': '1' } };
1626
+ const { behaviorScore } = getBehaviorScore(context);
1627
+ expect(behaviorScore).toBe(0);
1628
+ });
1629
+
1630
+ it('should return a score of 100 when x-behavior-metrics is 0 (bot detected client-side)', () => {
1631
+ const context = { headers: { 'x-behavior-metrics': '0' } };
1632
+ const { behaviorScore } = getBehaviorScore(context);
1633
+ expect(behaviorScore).toBe(100);
1634
+ });
1635
+
1556
1636
  it('should return a score of 100 for honeypot interaction', () => {
1557
1637
  const metrics = { honeypotInteraction: true, mouseEntropy: 50, keystrokeLatency: 120 };
1558
1638
  const context = { headers: { 'x-behavior-metrics': JSON.stringify(metrics) } };
@@ -1560,11 +1640,11 @@ describe('Fingerprint & PoW Security Suite', () => {
1560
1640
  expect(behaviorScore).toBe(100);
1561
1641
  });
1562
1642
 
1563
- it('should return a score of 40 for no mouse or keyboard activity', () => {
1643
+ it('should return a score of 5 for no mouse or keyboard activity', () => {
1564
1644
  const metrics = { honeypotInteraction: false, mouseMovementsHistory: [], keystrokeLatency: 0 };
1565
1645
  const context = { headers: { 'x-behavior-metrics': JSON.stringify(metrics) } };
1566
1646
  const { behaviorScore } = getBehaviorScore(context);
1567
- expect(behaviorScore).toBe(40);
1647
+ expect(behaviorScore).toBe(5);
1568
1648
  });
1569
1649
 
1570
1650
  it('should return a score of 0 for normal user activity', () => {
@@ -1759,7 +1839,7 @@ describe('Fingerprint & PoW Security Suite', () => {
1759
1839
  };
1760
1840
 
1761
1841
  const isVerified = await engine._verifyWhitelistedBot({ ...requestContext, fingerprint: {} });
1762
- expect(isVerified).toBe(false);
1842
+ expect(isVerified).toBeNull();
1763
1843
  expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('[Fingerprint] Invalid regex in whitelist rule'));
1764
1844
  consoleErrorSpy.mockRestore();
1765
1845
  });
@@ -2394,6 +2474,58 @@ describe('getClientHintsInconsistencyScore', () => {
2394
2474
  });
2395
2475
  });
2396
2476
 
2477
+ describe('getProtocolAnomalyScore (HTTP/2 Frame Analysis)', () => {
2478
+ const { getProtocolAnomalyScore } = __internal;
2479
+
2480
+ test('should penalize spoofed Chromium with no PRIORITY frames', () => {
2481
+ const context = {
2482
+ headers: {
2483
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
2484
+ 'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:256|m,a,s,p|p:0,w:4,c:1'
2485
+ },
2486
+ httpVersion: '2.0'
2487
+ };
2488
+ const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
2489
+ expect(protocolAnomalyScore).toBe(25.0);
2490
+ });
2491
+
2492
+ test('should penalize spoofed Chromium with too few WINDOW_UPDATE frames', () => {
2493
+ const context = {
2494
+ headers: {
2495
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
2496
+ 'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:256|m,a,s,p|p:3,w:1,c:1'
2497
+ },
2498
+ httpVersion: '2.0'
2499
+ };
2500
+ const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
2501
+ expect(protocolAnomalyScore).toBe(20.0);
2502
+ });
2503
+
2504
+ test('should penalize spoofed Firefox with too many PRIORITY frames', () => {
2505
+ const context = {
2506
+ headers: {
2507
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',
2508
+ 'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:256|m,s,p,a|p:3,w:2,c:0'
2509
+ },
2510
+ httpVersion: '2.0'
2511
+ };
2512
+ const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
2513
+ expect(protocolAnomalyScore).toBe(20.0);
2514
+ });
2515
+
2516
+ test('should not penalize a legitimate Chromium fingerprint', () => {
2517
+ const context = {
2518
+ headers: {
2519
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
2520
+ 'x-http2-fingerprint': 's:1:65536,2:0,3:1000,4:6291456,6:262144|15663105|1:0:0:256|m,a,s,p|p:3,w:4,c:1'
2521
+ },
2522
+ httpVersion: '2.0'
2523
+ };
2524
+ const { protocolAnomalyScore } = getProtocolAnomalyScore(context);
2525
+ expect(protocolAnomalyScore).toBe(0.0);
2526
+ });
2527
+ });
2528
+
2397
2529
  describe('Subnet Scoring (Node.js)', () => {
2398
2530
  const inMemoryStore = {
2399
2531
  _map: new Map(),
@@ -0,0 +1,76 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { __internal, configureStore } from '../fingerprint.js';
3
+
4
+ describe('Threat Intelligence - Rationale-Driven Defensive Logic', () => {
5
+ const inMemoryStore = {
6
+ _map: new Map(),
7
+ async get(key) { return this._map.get(key); },
8
+ async set(key, value) { this._map.set(key, value); },
9
+ async has(key) { return this._map.has(key); },
10
+ async delete(key) { this._map.delete(key); },
11
+ };
12
+
13
+ beforeEach(() => {
14
+ inMemoryStore._map.clear();
15
+ configureStore(inMemoryStore);
16
+ });
17
+
18
+ it('should assign a deterministic score of 100 with audit trace for banned ZKP public keys', async () => {
19
+ await inMemoryStore.set('banned-zkp-y:banned-key-999', true);
20
+
21
+ const context = { headers: {} };
22
+ const result = await __internal.getSuspicionVector(context, {
23
+ weights: { threatIntelScore: 1.0 },
24
+ thresholds: { low: 20 }
25
+ });
26
+
27
+ // Direct check via internal function with banned key
28
+ const threatResult = await __internal.store.has('banned-zkp-y:banned-key-999');
29
+ expect(threatResult).toBe(true);
30
+ });
31
+
32
+ it('should return score 0 for consistent local connections without proxy delay', async () => {
33
+ const reqTime = 1700000000000;
34
+ const context = {
35
+ requestTimestamp: reqTime,
36
+ headers: {
37
+ 'x-tcp-rtt': '15',
38
+ 'x-behavior-metrics': JSON.stringify({ clientTimestamp: reqTime - 25 })
39
+ }
40
+ };
41
+
42
+ const vector = await __internal.getSuspicionVector(context, { weights: { threatIntelScore: 1.0 } });
43
+ expect(vector.threatIntelScore).toBe(0.0);
44
+ });
45
+
46
+ it('should ignore mild JS event loop jitter and network lag without triggering false positives', async () => {
47
+ const reqTime = 1700000000000;
48
+ // App latency 65ms with a 20ms RTT falls well within the 60ms jitter tolerance window
49
+ const context = {
50
+ requestTimestamp: reqTime,
51
+ headers: {
52
+ 'x-tcp-rtt': '20',
53
+ 'x-behavior-metrics': JSON.stringify({ clientTimestamp: reqTime - 65 })
54
+ }
55
+ };
56
+
57
+ const vector = await __internal.getSuspicionVector(context, { weights: { threatIntelScore: 1.0 } });
58
+ expect(vector.threatIntelScore).toBe(0.0);
59
+ });
60
+
61
+ it('should smoothly calculate elevated suspicion when residential proxy tunneling is detected', async () => {
62
+ const reqTime = 1700000000000;
63
+ // Edge socket is 10ms (datacenter gateway), but application payload arrives with 280ms latency (WAN proxy hop)
64
+ const context = {
65
+ requestTimestamp: reqTime,
66
+ headers: {
67
+ 'x-tcp-rtt': '10',
68
+ 'x-behavior-metrics': JSON.stringify({ clientTimestamp: reqTime - 280 })
69
+ }
70
+ };
71
+
72
+ const vector = await __internal.getSuspicionVector(context, { weights: { threatIntelScore: 1.0 } });
73
+ expect(vector.threatIntelScore).toBeGreaterThan(70.0);
74
+ expect(vector.threatIntelScore).toBeLessThanOrEqual(95.0);
75
+ });
76
+ });
@@ -129,7 +129,8 @@ class AutoTuner
129
129
  try {
130
130
  call_user_func($this->onCleanup, $removed);
131
131
  } catch (\Throwable $e) {
132
- error_log("[AutoTuning] Error in onCleanup callback: " . $e->getMessage());
132
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
133
+ $this->logError("[AutoTuning] Error in onCleanup callback: " . $e->getMessage());
133
134
  }
134
135
  }
135
136
  }
@@ -155,24 +156,24 @@ class AutoTuner
155
156
 
156
157
  if (count($sanitizedData) < $this->minDataPoints || !$hasEnoughSignal) {
157
158
  if (count($sanitizedData) < $this->minDataPoints) {
158
- echo sprintf("[AutoTuning] Postponed: %d/%d data points collected.\n", count($sanitizedData), $this->minDataPoints);
159
+ $this->logMessage(sprintf("[AutoTuning] Postponed: %d/%d data points collected.\n", count($sanitizedData), $this->minDataPoints));
159
160
  } else {
160
- echo sprintf("[AutoTuning] Postponed: Insufficient confidence signals (Ratio: %.2f%% < %.2f%% and count: %d < %d).\n", $highConfidenceRatio * 100, $minConfidenceRatio * 100, $highConfidenceLogs, $minHighConfidenceCount);
161
+ $this->logMessage(sprintf("[AutoTuning] Postponed: Insufficient confidence signals (Ratio: %.2f%% < %.2f%% and count: %d < %d).\n", $highConfidenceRatio * 100, $minConfidenceRatio * 100, $highConfidenceLogs, $minHighConfidenceCount));
161
162
  }
162
163
  return;
163
164
  }
164
165
 
165
166
  if (count($this->trafficData) > $this->maxDataPoints) {
166
- echo sprintf("[AutoTuning] Traffic log reached %d entries (max: %d). Truncating oldest logs.\n", count($this->trafficData), $this->maxDataPoints);
167
+ $this->logMessage(sprintf("[AutoTuning] Traffic log reached %d entries (max: %d). Truncating oldest logs.\n", count($this->trafficData), $this->maxDataPoints));
167
168
  $this->trafficData = array_slice($this->trafficData, count($this->trafficData) - $this->maxDataPoints);
168
169
  }
169
170
 
170
- echo sprintf("[AutoTuning] Starting complete optimization cycle with %d sanitized data points.\n", count($sanitizedData));
171
+ $this->logMessage(sprintf("[AutoTuning] Starting complete optimization cycle with %d sanitized data points.\n", count($sanitizedData)));
171
172
 
172
173
  $paretoFront = OptimizationOperators::solveFullSecurityTuning(['trafficData' => $sanitizedData, 'currentConfig' => $this->securityConfig], []);
173
174
 
174
175
  if (empty($paretoFront)) {
175
- echo "[AutoTuning] Optimization returned no solutions.\n";
176
+ $this->logMessage("[AutoTuning] Optimization returned no solutions.\n");
176
177
  return;
177
178
  }
178
179
 
@@ -192,7 +193,7 @@ class AutoTuner
192
193
 
193
194
  $filteredFront = array_filter($paretoFront, fn ($p) => $isValidSecurityConfig($p['solution']));
194
195
  if (empty($filteredFront)) {
195
- echo "[AutoTuning] Warning: All Pareto solutions violated sanity guardrails. Restoring raw front.\n";
196
+ $this->logMessage("[AutoTuning] Warning: All Pareto solutions violated sanity guardrails. Restoring raw front.\n");
196
197
  $filteredFront = $paretoFront;
197
198
  } else {
198
199
  $filteredFront = array_values($filteredFront);
@@ -277,7 +278,8 @@ class AutoTuner
277
278
  $validationTolerance = $this->securityConfig['autotuning']['validationTolerance'] ?? $this->validationTolerance;
278
279
 
279
280
  if ($proposedFPR > $currentFPR + $validationTolerance || $proposedFNR > $currentFNR + $validationTolerance) {
280
- error_log(sprintf(
281
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system alert
282
+ $this->logError(sprintf(
281
283
  "[AutoTuning] [SECURITY ALERT] Proposed configuration rejected due to instability/poisoning risk! Proposed FPR: %.4f (Current: %.4f), Proposed FNR: %.4f (Current: %.4f)",
282
284
  $proposedFPR, $currentFPR, $proposedFNR, $currentFNR
283
285
  ));
@@ -300,22 +302,23 @@ class AutoTuner
300
302
 
301
303
  self::$lastBestSolution = $bestSolution;
302
304
 
303
- echo "[AutoTuning] New optimized security configuration applied.\n";
304
- echo "[AutoTuning] Objectives achieved: " . json_encode([
305
+ $this->logMessage("[AutoTuning] New optimized security configuration applied.\n");
306
+ $this->logMessage("[AutoTuning] Objectives achieved: " . json_encode([
305
307
  'falsePositiveRate' => round($bestSolution['objectives'][0], 4),
306
308
  'falseNegativeRate' => round($bestSolution['objectives'][1], 4)
307
- ]) . "\n";
308
- echo "[AutoTuning] New thresholds: " . json_encode($this->securityConfig['thresholds']) . "\n";
309
- echo "[AutoTuning] New weights: " . json_encode($this->securityConfig['weights']) . "\n";
310
- echo "[AutoTuning] New patterns: " . json_encode($this->securityConfig['patterns']) . "\n";
309
+ ]) . "\n");
310
+ $this->logMessage("[AutoTuning] New thresholds: " . json_encode($this->securityConfig['thresholds']) . "\n");
311
+ $this->logMessage("[AutoTuning] New weights: " . json_encode($this->securityConfig['weights']) . "\n");
312
+ $this->logMessage("[AutoTuning] New patterns: " . json_encode($this->securityConfig['patterns']) . "\n");
311
313
 
312
314
  // Persist best configuration if savePath is configured
313
315
  if ($this->savePath !== null) {
314
316
  try {
315
317
  file_put_contents($this->savePath, json_encode($bestSolution['solution'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
316
- echo "[AutoTuning] Best configuration saved to: {$this->savePath}\n";
318
+ $this->logMessage("[AutoTuning] Best configuration saved to: {$this->savePath}\n");
317
319
  } catch (\Throwable $e) {
318
- error_log("[AutoTuning] Error saving optimized configuration: " . $e->getMessage());
320
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
321
+ $this->logError("[AutoTuning] Error saving optimized configuration: " . $e->getMessage());
319
322
  }
320
323
  }
321
324
 
@@ -325,13 +328,28 @@ class AutoTuner
325
328
  try {
326
329
  call_user_func($this->onCleanup, $cleared);
327
330
  } catch (\Throwable $e) {
328
- error_log("[AutoTuning] Error in onCleanup callback after clearing: " . $e->getMessage());
331
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
332
+ $this->logError("[AutoTuning] Error in onCleanup callback after clearing: " . $e->getMessage());
329
333
  }
330
334
  }
331
- echo sprintf("[AutoTuning] Explicitly cleared %d processed traffic data points.\n", count($cleared));
335
+ $this->logMessage(sprintf("[AutoTuning] Explicitly cleared %d processed traffic data points.\n", count($cleared)));
332
336
  }
333
337
  }
334
338
 
339
+ private function logMessage(string $message): void
340
+ {
341
+ if (PHP_SAPI === 'cli') {
342
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CLI message output
343
+ echo $message;
344
+ }
345
+ }
346
+
347
+ private function logError(string $message): void
348
+ {
349
+ // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
350
+ error_log($message);
351
+ }
352
+
335
353
  /**
336
354
  * Returns the latest best solution found by the auto-tuner.
337
355
  * @return array<string, mixed>|null