@anonympins/fingerprint 0.7.3 → 0.7.5

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.
@@ -233,7 +233,7 @@ class RequestUtilsTest extends TestCase
233
233
  'headers' => ['x-behavior-metrics' => json_encode($metricsNoActivity)]
234
234
  ]);
235
235
  $scoreNoActivity = RequestUtils::getBehaviorScore($contextNoActivity);
236
- $this->assertEquals(40.0, $scoreNoActivity['behaviorScore']);
236
+ $this->assertEquals(5.0, $scoreNoActivity['behaviorScore']);
237
237
  }
238
238
 
239
239
  public function testGetBehaviorScoreWithBotLikeKeystrokeDynamics(): void
@@ -61,4 +61,38 @@ class ThreatIntelTest extends TestCase
61
61
  $this->assertGreaterThan(75.0, $score);
62
62
  $this->assertLessThanOrEqual(95.0, $score);
63
63
  }
64
+
65
+ public function testDifferentialPrivacyDecoyInjectionAndTimestampNoise(): void
66
+ {
67
+ $config = [
68
+ 'federatedPeers' => ['https://peer1.example.com'],
69
+ 'federationSecret' => 'test-secret-key-32-chars-long!!',
70
+ 'differentialPrivacy' => [
71
+ 'enabled' => true,
72
+ 'epsilon' => 1.0,
73
+ 'dummyRate' => 1.0 // Forcer l'injection d'un leurre pour le test
74
+ ]
75
+ ];
76
+
77
+ $postedCalls = [];
78
+ $engineSub = new class($config, $postedCalls) extends \Anonympins\Fingerprint\FingerprintEngine {
79
+ public array $calls = [];
80
+ public function __construct(array $cfg, array &$callsRef) {
81
+ $this->calls = &$callsRef;
82
+ parent::__construct($cfg);
83
+ }
84
+ protected function asyncPost(string $url, array $params): void {
85
+ $this->calls[] = ['url' => $url, 'params' => $params];
86
+ }
87
+ };
88
+
89
+ $refSub = new \ReflectionClass($engineSub);
90
+ $broadcastMethod = $refSub->getMethod('broadcastBannedZkp');
91
+ $broadcastMethod->setAccessible(true);
92
+
93
+ $realZkpY = 'realzkpykey12345';
94
+ $broadcastMethod->invoke($engineSub, $realZkpY);
95
+
96
+ $this->assertCount(2, $postedCalls, 'Doit diffuser à la fois la clé réelle et la clé leurre');
97
+ }
64
98
  }
@@ -116,6 +116,54 @@ class RequestUtils
116
116
  return (string)$srv;
117
117
  }
118
118
 
119
+ /**
120
+ * Analyzes TCP MTU and fragmentation flags to detect network tunnels (VPN/Proxy).
121
+ * @param RequestContext $context The request context.
122
+ * @return array{'mtuAnomalyScore': float}
123
+ */
124
+ public static function getMtuAnomalyScore(RequestContext $context): array
125
+ {
126
+ $mtuHeader = $context->getHeader('x-tcp-mtu-info');
127
+ if (empty($mtuHeader) || !is_string($mtuHeader)) {
128
+ return ['mtuAnomalyScore' => 0.0];
129
+ }
130
+
131
+ $parts = explode(':', $mtuHeader);
132
+ if (count($parts) < 2) {
133
+ return ['mtuAnomalyScore' => 0.0];
134
+ }
135
+
136
+ $mtu = filter_var($parts[0], FILTER_VALIDATE_INT);
137
+ $df = filter_var($parts[1], FILTER_VALIDATE_INT);
138
+
139
+ if ($mtu === false || $df === false) {
140
+ return ['mtuAnomalyScore' => 0.0];
141
+ }
142
+
143
+ $score = 0.0;
144
+
145
+ // 1. Pénalité modérée pour les MTU typiques des VPNs/tunnels.
146
+ if ($mtu > 1200 && $mtu <= 1420) {
147
+ $score += 35.0;
148
+ } elseif ($mtu > 1420 && $mtu < 1492) {
149
+ $score += 20.0;
150
+ }
151
+
152
+ // 2. OS vs. Network Stack Inconsistency
153
+ $ua = $context->getHeader('user-agent') ?? '';
154
+ $uaParts = self::parseUserAgent($ua);
155
+ $os = $uaParts['os'] ?? null;
156
+
157
+ // Pénalités additionnelles en cas d'incohérence OS vs signature réseau.
158
+ if ($os) {
159
+ if (str_starts_with($os, 'Windows') && $mtu < 1492) $score += 20.0;
160
+ if ((str_starts_with($os, 'Android') || str_starts_with($os, 'iOS')) && $mtu < 1480) $score += 15.0;
161
+ if ($df === 0 && (str_starts_with($os, 'Windows') || str_starts_with($os, 'Mac') || str_starts_with($os, 'Linux'))) $score += 40.0;
162
+ }
163
+
164
+ return ['mtuAnomalyScore' => min(100.0, $score)];
165
+ }
166
+
119
167
  /**
120
168
  * Calcule un score d'incohérence entre la signature TLS (JA3) et le User-Agent.
121
169
  * @return array{'tlsSpoofingScore': float}
@@ -392,125 +440,145 @@ class RequestUtils
392
440
 
393
441
  /**
394
442
  * Calcule un score basé sur les métriques comportementales envoyées par le client.
395
- * @return array{'behaviorScore': float}
443
+ * @param RequestContext $context Le contexte de la requête.
444
+ * @return array{behaviorScore: float}
396
445
  */
397
446
  public static function getBehaviorScore(RequestContext $context): array
398
447
  {
399
- $behaviorHeader = $context->getHeader('x-behavior-metrics');
400
- if (!$behaviorHeader) {
448
+ $header = $context->getHeader('x-behavior-metrics');
449
+ if (empty($header)) {
401
450
  return ['behaviorScore' => 0.0];
402
451
  }
403
452
 
404
- $metrics = json_decode($behaviorHeader, true);
405
- if (json_last_error() !== JSON_ERROR_NONE) {
406
- return ['behaviorScore' => 10.0]; // En-tête malformé
453
+ if ($header === '1' || $header === 1) {
454
+ return ['behaviorScore' => 0.0];
407
455
  }
408
-
409
- if (!empty($metrics['honeypotInteraction'])) {
456
+ if ($header === '0' || $header === 0) {
410
457
  return ['behaviorScore' => 100.0];
411
458
  }
412
459
 
413
- $score = 0.0;
414
- if (!empty($metrics['prototypeTampered'])) {
415
- $score += 80.0;
460
+ if (is_numeric($header)) {
461
+ $score = max(0.0, min(100.0, (float)$header));
462
+ return ['behaviorScore' => $score];
416
463
  }
417
464
 
418
- $mouseAnalysis = self::analyzeMouseMovements($metrics['mouseMovementsHistory'] ?? null);
419
- $touch = self::analyzeTouchMovements($metrics['touchMovementsHistory'] ?? null);
420
-
421
- if (isset($metrics['historyLength'])) {
422
- if ($metrics['historyLength'] === 1) $score += 15;
423
- elseif ($metrics['historyLength'] >= 5) $score -= 20;
424
- elseif ($metrics['historyLength'] >= 2) $score -= 10;
425
- } else {
426
- // Pénalité pour absence totale d'interaction si l'historique n'est pas dispo
427
- if ($mouseAnalysis['avgSpeed'] == 0 && $touch['avgSpeed'] == 0 && ($metrics['keystrokeLatency'] ?? 0) == 0) {
428
- $score += 40;
465
+ if (is_string($header)) {
466
+ $trimmed = trim($header);
467
+ if (is_numeric($trimmed)) {
468
+ if ($trimmed === '1') {
469
+ return ['behaviorScore' => 0.0];
470
+ }
471
+ if ($trimmed === '0') {
472
+ return ['behaviorScore' => 100.0];
473
+ }
474
+ $score = max(0.0, min(100.0, (float)$trimmed));
475
+ return ['behaviorScore' => $score];
429
476
  }
430
477
  }
431
478
 
432
- if ($mouseAnalysis['avgSpeed'] > 0) {
433
- if ($mouseAnalysis['avgSpeed'] > 3) $score += 25;
434
- if ($mouseAnalysis['avgAcceleration'] > 0.5) $score += 20;
435
- if ($mouseAnalysis['straightness'] > 0.95) $score += 30;
436
- if ($mouseAnalysis['pauses'] === 0 && count($mouseAnalysis['segments']) > 20) $score += 15;
479
+ $metrics = json_decode($header, true);
480
+ if (json_last_error() !== JSON_ERROR_NONE) {
481
+ return ['behaviorScore' => 10.0];
437
482
  }
438
483
 
439
- if (($metrics['keystrokeLatency'] ?? 0) > 0 && $metrics['keystrokeLatency'] < 40) $score += 25;
440
- if (($metrics['keystrokeLatency'] ?? 0) > 1000) $score += 15;
484
+ if ($metrics === 1) {
485
+ return ['behaviorScore' => 0.0];
486
+ }
487
+ if ($metrics === 0) {
488
+ return ['behaviorScore' => 100.0];
489
+ }
441
490
 
442
- // NOUVEAU: Analyse de digraphie/trigraphie (dwell & flight times)
443
- $dwellTimes = $metrics['keystrokeDwellTimes'] ?? [];
444
- $flightTimes = $metrics['keystrokeFlightTimes'] ?? [];
491
+ if (is_numeric($metrics)) {
492
+ $score = max(0.0, min(100.0, (float)$metrics));
493
+ return ['behaviorScore' => $score];
494
+ }
445
495
 
446
- if (count($dwellTimes) >= 5) {
447
- $meanDwell = array_sum($dwellTimes) / count($dwellTimes);
448
- $varDwell = array_reduce($dwellTimes, fn($carry, $item) => $carry + pow($item - $meanDwell, 2), 0) / count($dwellTimes);
449
- $stdDevDwell = sqrt($varDwell);
496
+ if (!is_array($metrics)) {
497
+ return ['behaviorScore' => 0.0];
498
+ }
450
499
 
451
- if ($stdDevDwell < 2.0) {
452
- $score += 35.0;
453
- }
454
- if ($meanDwell < 15.0) {
455
- $score += 25.0;
456
- }
500
+ if ($metrics['honeypotInteraction'] ?? false) {
501
+ return ['behaviorScore' => 100.0];
457
502
  }
458
503
 
459
- if (count($flightTimes) >= 5) {
460
- $times = array_column($flightTimes, 'time');
461
- $meanFlight = array_sum($times) / count($times);
462
- $varFlight = array_reduce($times, fn($carry, $item) => $carry + pow($item - $meanFlight, 2), 0) / count($times);
463
- $stdDevFlight = sqrt($varFlight);
504
+ $score = 0.0;
505
+ if ($metrics['prototypeTampered'] ?? false) {
506
+ $score += 80.0;
507
+ }
464
508
 
465
- if ($stdDevFlight < 3.0) {
466
- $score += 35.0;
467
- }
468
- if ($meanFlight < 25.0) {
469
- $score += 25.0;
470
- }
471
- $benfordDev = Optimization::benfordTest($times);
472
- if ($benfordDev > 0.18) {
473
- $score += 30.0;
509
+ $mouseHistory = $metrics['mouseMovementsHistory'] ?? [];
510
+ $touchHistory = $metrics['touchMovementsHistory'] ?? [];
511
+
512
+ $mouseAnalysis = RequestUtils::analyzeMouseMovements($mouseHistory);
513
+ $touchAnalysis = RequestUtils::analyzeTouchMovements($touchHistory);
514
+
515
+ $mouseAvgSpeed = (float)($mouseAnalysis['avgSpeed'] ?? 0.0);
516
+ $touchAvgSpeed = (float)($touchAnalysis['avgSpeed'] ?? 0.0);
517
+ $keystrokeLatency = (float)($metrics['keystrokeLatency'] ?? 0.0);
518
+
519
+ if (isset($metrics['historyLength'])) {
520
+ $historyLength = (int)$metrics['historyLength'];
521
+ if ($historyLength === 1) $score += 15;
522
+ elseif ($historyLength >= 5) $score -= 20;
523
+ elseif ($historyLength >= 2) $score -= 10;
524
+ } else {
525
+ if ($mouseAvgSpeed === 0.0 && $touchAvgSpeed === 0.0 && $keystrokeLatency === 0.0) {
526
+ $noInteractionPenalty = 5.0;
527
+ if ($metrics['rendering']['offscreenAnom'] ?? false) {
528
+ $noInteractionPenalty += 40.0;
529
+ }
530
+ $score += $noInteractionPenalty;
474
531
  }
475
532
  }
476
533
 
477
- // Analyse de Benford sur les segments de mouvement de la souris
478
- if (count($mouseAnalysis['segments']) > 10) {
479
- $benfordDeviation = Optimization::benfordTest($mouseAnalysis['segments']);
480
- if ($benfordDeviation > 0.18) {
481
- $score += 35;
482
- }
534
+ if ($mouseAvgSpeed > 0) {
535
+ if ($mouseAvgSpeed > 3.0) $score += 25;
536
+ if (($mouseAnalysis['avgAcceleration'] ?? 0.0) > 0.5) $score += 20;
537
+ if (($mouseAnalysis['straightness'] ?? 1.0) > 0.95) $score += 30;
538
+ if (($mouseAnalysis['pauses'] ?? 0) === 0 && count($mouseAnalysis['segments'] ?? []) > 20) $score += 15;
483
539
  }
484
- // Analyse comportementale des événements tactiles (Touch Move)
485
- $touchHistory = $metrics['touchMovementsHistory'] ?? null;
486
- if (!empty($touchHistory)) {
487
- if ($touch['avgSpeed'] > 0) {
488
- if ($touch['avgSpeed'] > 5) $score += 30;
489
- if ($touch['avgAcceleration'] > 0.8) $score += 20;
490
- if ($touch['straightness'] > 0.98) $score += 35;
491
- if ($touch['pauses'] === 0 && count($touch['segments']) > 25) $score += 15;
492
540
 
493
- // Détection de l'émulation (pression et rayon de contact constants)
494
- if ($touch['avgPressure'] > 0 && $touch['pressureVariance'] == 0) {
495
- $score += 30;
541
+ if ($touchAvgSpeed > 0 || !empty($touchHistory)) {
542
+ if (count($touchHistory) >= 3) {
543
+ if (($touchAnalysis['pressureVariance'] ?? 1.0) < 0.0001) {
544
+ $score += 35.0;
545
+ }
546
+ if (($touchAnalysis['radiusVariance'] ?? 1.0) < 0.0001) {
547
+ $score += 25.0;
496
548
  }
497
- if ($touch['avgRadius'] > 0 && $touch['radiusVariance'] == 0) {
498
- $score += 30;
549
+ if (($touchAnalysis['straightness'] ?? 0.0) > 0.98) {
550
+ $score += 20.0;
499
551
  }
500
552
  }
501
- if (count($touch['segments']) > 10) {
502
- $benfordDev = Optimization::benfordTest($touch['segments']);
503
- if ($benfordDev > 0.18) {
504
- $score += 35;
553
+ }
554
+
555
+ $dwellTimes = $metrics['keystrokeDwellTimes'] ?? [];
556
+ if (is_array($dwellTimes) && count($dwellTimes) >= 3) {
557
+ $dwellNumeric = array_filter($dwellTimes, 'is_numeric');
558
+ if (count($dwellNumeric) >= 3) {
559
+ $meanDwell = array_sum($dwellNumeric) / count($dwellNumeric);
560
+ $dwellVar = array_reduce($dwellNumeric, fn($carry, $val) => $carry + pow((float)$val - $meanDwell, 2), 0.0) / count($dwellNumeric);
561
+ if ($dwellVar < 2.0) {
562
+ $score += 40.0;
505
563
  }
506
564
  }
565
+ }
507
566
 
508
- // Détection de ferme mobile : Touch actif sur mobile sans aucune vibration physique (châssis/rack ADB)
509
- $ua = $context->getHeader('user-agent') ?? '';
510
- $isMobileDevice = str_contains($ua, 'Mobile');
511
- if ($isMobileDevice && count($touchHistory) >= 5 && isset($metrics['motionVariance']) && is_numeric($metrics['motionVariance'])) {
512
- if ((float)$metrics['motionVariance'] === 0.0) {
513
- $score += 50.0;
567
+ $flightTimes = $metrics['keystrokeFlightTimes'] ?? [];
568
+ if (is_array($flightTimes) && count($flightTimes) >= 3) {
569
+ $extractedFlightTimes = [];
570
+ foreach ($flightTimes as $ft) {
571
+ if (is_array($ft) && isset($ft['time']) && is_numeric($ft['time'])) {
572
+ $extractedFlightTimes[] = (float)$ft['time'];
573
+ } elseif (is_numeric($ft)) {
574
+ $extractedFlightTimes[] = (float)$ft;
575
+ }
576
+ }
577
+ if (count($extractedFlightTimes) >= 3) {
578
+ $meanFlight = array_sum($extractedFlightTimes) / count($extractedFlightTimes);
579
+ $flightVar = array_reduce($extractedFlightTimes, fn($carry, $val) => $carry + pow($val - $meanFlight, 2), 0.0) / count($extractedFlightTimes);
580
+ if ($flightVar < 2.0) {
581
+ $score += 40.0;
514
582
  }
515
583
  }
516
584
  }
@@ -518,6 +586,7 @@ class RequestUtils
518
586
  return ['behaviorScore' => min(100.0, $score)];
519
587
  }
520
588
 
589
+
521
590
  /**
522
591
  * Calcule un score basé sur la régularité d'affichage (V-Sync) et l'utilisation suspecte d'OffscreenCanvas.
523
592
  * @return array{'renderingAnomalyScore': float}
@@ -2008,6 +2077,21 @@ class RequestUtils
2008
2077
  return ['tcpAnomalyScore' => $tcpAnomalyScore];
2009
2078
  }
2010
2079
 
2080
+ /**
2081
+ * Helper to find the first key of a value from a list of needles in an array.
2082
+ * @param array<int, string> $haystack
2083
+ * @param array<int, string> $needles
2084
+ * @return int|null
2085
+ */
2086
+ private static function array_find_key(array $haystack, array $needles): ?int
2087
+ {
2088
+ foreach ($needles as $needle) {
2089
+ $key = array_search($needle, $haystack, true);
2090
+ if ($key !== false) return (int)$key;
2091
+ }
2092
+ return null;
2093
+ }
2094
+
2011
2095
  /**
2012
2096
  * Détecte les anomalies de protocole (HTTP/2 et QUIC/HTTP3) par rapport au User-Agent.
2013
2097
  * @param RequestContext $context
@@ -2022,51 +2106,95 @@ class RequestUtils
2022
2106
  $uaParts = self::parseUserAgent($ua);
2023
2107
  $browser = $uaParts['browser'] ?? null;
2024
2108
 
2025
- // HTTP/2 Anomaly logic
2026
- $h2Fp = $context->getHeader('x-http2-fingerprint') ?? $context->http2Fingerprint ?? null;
2027
- if ($h2Fp && is_string($h2Fp) && $browser) {
2028
- $parts = explode('|', $h2Fp);
2029
- if (count($parts) >= 4) {
2030
- $connWindow = (int)$parts[1];
2031
- $headerOrder = $parts[3];
2032
- $isChromium = str_starts_with($browser, 'Chrome') || str_starts_with($browser, 'Edge');
2033
- $isFirefox = str_starts_with($browser, 'Firefox');
2034
- $isSafari = str_starts_with($browser, 'Safari');
2035
-
2036
- if ($isChromium) {
2037
- if ($headerOrder && $headerOrder !== 'm,a,s,p') $http2Anomaly += 60.0;
2038
- if ($connWindow === 65535 || $connWindow === 65536) $http2Anomaly += 40.0;
2039
- } elseif ($isFirefox) {
2040
- if ($headerOrder && $headerOrder !== 'm,s,p,a') $http2Anomaly += 60.0;
2041
- } elseif ($isSafari) {
2042
- if ($headerOrder && $headerOrder !== 'm,s,p,a') $http2Anomaly += 60.0;
2109
+ if ($browser) {
2110
+ // HTTP/2 Anomaly logic
2111
+ $h2Fp = $context->getHeader('x-http2-fingerprint') ?? $context->http2Fingerprint ?? null;
2112
+ if ($h2Fp && is_string($h2Fp)) {
2113
+ $parts = explode('|', $h2Fp);
2114
+ if (count($parts) >= 3) {
2115
+ $connWindow = (int)$parts[1];
2116
+ $streamPriority = $parts[2] ?? '';
2117
+ $headerOrder = $parts[3] ?? '';
2118
+ $isChromium = str_starts_with($browser, 'Chrome') || str_starts_with($browser, 'Edge');
2119
+ $isFirefox = str_starts_with($browser, 'Firefox');
2120
+ $isSafari = str_starts_with($browser, 'Safari');
2121
+
2122
+ if ($isChromium) {
2123
+ if ($headerOrder && $headerOrder !== 'm,a,s,p') $http2Anomaly += 60.0;
2124
+ if ($connWindow === 65535 || $connWindow === 65536) $http2Anomaly += 40.0;
2125
+ if ($streamPriority === '0' || $streamPriority === '') $http2Anomaly += 50.0;
2126
+ } elseif ($isFirefox) {
2127
+ if ($headerOrder && $headerOrder !== 'm,s,p,a') $http2Anomaly += 60.0;
2128
+ } elseif ($isSafari) {
2129
+ if ($headerOrder && $headerOrder !== 'm,s,p,a') $http2Anomaly += 60.0;
2130
+ }
2043
2131
  }
2044
2132
  }
2045
- }
2046
2133
 
2047
- // QUIC Anomaly logic
2048
- $quicFp = $context->getHeader('x-quic-fp') ?? $context->quicFingerprint ?? null;
2049
- if ($quicFp && is_string($quicFp) && $browser) {
2050
- $parts = explode(';', $quicFp);
2051
- if (count($parts) >= 2) {
2052
- $params = [];
2053
- foreach (explode(',', $parts[1]) as $p) {
2054
- $kv = explode('=', $p, 2);
2055
- if (count($kv) === 2) {
2056
- $params[$kv[0]] = $kv[1];
2134
+ // QUIC Anomaly logic
2135
+ $quicFp = $context->getHeader('x-quic-fp') ?? $context->quicFingerprint ?? null;
2136
+ if ($quicFp && is_string($quicFp)) {
2137
+ $parts = explode(';', $quicFp);
2138
+ if (count($parts) >= 2) {
2139
+ $params = [];
2140
+ foreach (explode(',', $parts[1]) as $p) {
2141
+ $kv = explode('=', $p, 2);
2142
+ if (count($kv) === 2) {
2143
+ $params[$kv[0]] = $kv[1];
2144
+ }
2145
+ }
2146
+ $priorityOrder = $parts[2] ?? '';
2147
+ $frameOrderRaw = $parts[3] ?? $context->getHeader('x-quic-frame-order') ?? '';
2148
+ $frameOrder = array_values(array_filter(array_map('trim', explode(',', strtolower($frameOrderRaw)))));
2149
+
2150
+ $isChromium = str_starts_with($browser, 'Chrome') || str_starts_with($browser, 'Edge');
2151
+ $isFirefox = str_starts_with($browser, 'Firefox');
2152
+ $isSafari = str_starts_with($browser, 'Safari');
2153
+
2154
+ $maxData = (int)($params['1'] ?? $params['0x01'] ?? 0);
2155
+ $maxStreams = (int)($params['4'] ?? $params['8'] ?? $params['0x08'] ?? 0);
2156
+ $bidiLocal = (int)($params['5'] ?? $params['0x05'] ?? 0);
2157
+ $bidiRemote = (int)($params['6'] ?? $params['0x06'] ?? 0);
2158
+
2159
+ if ($isChromium) {
2160
+ if ($maxData > 0 && $maxData < 1048576) $quicAnomaly += 40.0;
2161
+ if ($maxStreams > 0 && $maxStreams !== 100) $quicAnomaly += 30.0;
2162
+ if (!empty($priorityOrder) && !str_contains($priorityOrder, 'u=')) $quicAnomaly += 30.0;
2163
+ if ($bidiLocal > 0 && ($bidiLocal < 524288 || $bidiLocal === 262144)) $quicAnomaly += 40.0;
2164
+ if ($bidiRemote > 0 && ($bidiRemote < 524288 || $bidiRemote === 262144)) $quicAnomaly += 30.0;
2165
+
2166
+ if (count($frameOrder) >= 2) {
2167
+ $sIdx = self::array_find_key($frameOrder, ['s', 'settings', '4']);
2168
+ $mIdx = self::array_find_key($frameOrder, ['m', 'max_streams', '18']);
2169
+ $pIdx = self::array_find_key($frameOrder, ['p', 'priority', 'priority_update', '15']);
2170
+
2171
+ if ($sIdx !== null && $sIdx !== 0) $quicAnomaly += 50.0;
2172
+ if ($mIdx !== null && $sIdx !== null && $mIdx < $sIdx) $quicAnomaly += 60.0;
2173
+ if ($pIdx !== null && $sIdx !== null && $pIdx < $sIdx) $quicAnomaly += 60.0;
2174
+ }
2175
+ } elseif ($isFirefox) {
2176
+ if ($maxData > 0 && $maxData > 5000000) $quicAnomaly += 40.0;
2177
+ if ($maxStreams === 100) $quicAnomaly += 50.0;
2178
+ if ($bidiLocal === 6291456) $quicAnomaly += 50.0;
2179
+
2180
+ if (count($frameOrder) >= 2) {
2181
+ $sIdx = self::array_find_key($frameOrder, ['s', 'settings', '4']);
2182
+ if ($sIdx !== null && $sIdx !== 0) $quicAnomaly += 50.0;
2183
+ }
2184
+ } elseif ($isSafari) {
2185
+ if ($maxStreams === 100 && $maxData === 1572864 && str_contains($priorityOrder, 'u=2,i')) {
2186
+ $quicAnomaly += 60.0;
2187
+ }
2188
+ if ($bidiLocal === 6291456) $quicAnomaly += 50.0;
2189
+
2190
+ if (count($frameOrder) >= 2) {
2191
+ $sIdx = self::array_find_key($frameOrder, ['s', 'settings', '4']);
2192
+ $mIdx = self::array_find_key($frameOrder, ['m', 'max_streams']);
2193
+ if ($mIdx !== null && ($mIdx === 0 || ($sIdx !== null && $mIdx < $sIdx))) {
2194
+ $quicAnomaly += 50.0;
2195
+ }
2196
+ }
2057
2197
  }
2058
- }
2059
- $priorityOrder = $parts[2] ?? '';
2060
-
2061
- if (str_starts_with($browser, 'Chrome') || str_starts_with($browser, 'Edge')) {
2062
- $maxData = isset($params['1']) ? (int)$params['1'] : 0;
2063
- $maxStreams = isset($params['4']) ? (int)$params['4'] : 0;
2064
- if ($maxData > 0 && $maxData < 1048576) $quicAnomaly += 40.0;
2065
- if ($maxStreams > 0 && $maxStreams !== 100) $quicAnomaly += 30.0;
2066
- if (!empty($priorityOrder) && !str_contains($priorityOrder, 'u=')) $quicAnomaly += 30.0;
2067
- } elseif (str_starts_with($browser, 'Firefox')) {
2068
- $maxData = isset($params['1']) ? (int)$params['1'] : 0;
2069
- if ($maxData > 0 && $maxData > 5000000) $quicAnomaly += 40.0;
2070
2198
  }
2071
2199
  }
2072
2200
  }
@@ -2076,7 +2204,9 @@ class RequestUtils
2076
2204
  0.0,
2077
2205
  min(100.0, $http2Anomaly),
2078
2206
  min(100.0, $quicAnomaly)
2079
- )
2207
+ ),
2208
+ 'http2AnomalyScore' => min(100.0, $http2Anomaly),
2209
+ 'quicAnomalyScore' => min(100.0, $quicAnomaly)
2080
2210
  ];
2081
2211
  }
2082
2212
 
@@ -3,7 +3,7 @@
3
3
  * Plugin Name: Fingerprint Anti-Bot & Proof-of-Work
4
4
  * Plugin URI: https://github.com/anonympins/fingerprint
5
5
  * Description: High-performance client-side anti-bot protection and Proof-of-Work challenge verification for WordPress.
6
- * Version: 0.7.3
6
+ * Version: 0.7.5
7
7
  * Author: anonympins
8
8
  * Requires at least: 5.9
9
9
  * Requires PHP: 8.0
@@ -1,6 +1,6 @@
1
1
  msgid ""
2
2
  msgstr ""
3
- "Project-Id-Version: Fingerprint Anti-Bot 0.7.3\n"
3
+ "Project-Id-Version: Fingerprint Anti-Bot 0.7.5\n"
4
4
  "Report-Msgid-Bugs-To: https://github.com/anonympins/fingerprint/issues\n"
5
5
  "POT-Creation-Date: 2026-09-24 12:00+0000\n"
6
6
  "PO-Revision-Date: 2026-09-24 12:00+0000\n"
@@ -1,6 +1,6 @@
1
1
  msgid ""
2
2
  msgstr ""
3
- "Project-Id-Version: Fingerprint Anti-Bot 0.7.3\n"
3
+ "Project-Id-Version: Fingerprint Anti-Bot 0.7.5\n"
4
4
  "Report-Msgid-Bugs-To: https://github.com/anonympins/fingerprint/issues\n"
5
5
  "POT-Creation-Date: 2026-09-24 12:00+0000\n"
6
6
  "PO-Revision-Date: 2026-09-24 12:00+0000\n"
@@ -4,7 +4,7 @@ Tags: bot protection, security, proof of work, firewall, anti scraping
4
4
  Requires at least: 5.9
5
5
  Tested up to: 7.1
6
6
  Requires PHP: 8.0
7
- Stable tag: 0.7.3
7
+ Stable tag: 0.7.5
8
8
  License: MIT
9
9
  License URI: https://opensource.org/licenses/MIT
10
10