@anonympins/fingerprint 0.7.3 → 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.
@@ -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
@@ -392,125 +392,145 @@ class RequestUtils
392
392
 
393
393
  /**
394
394
  * Calcule un score basé sur les métriques comportementales envoyées par le client.
395
- * @return array{'behaviorScore': float}
395
+ * @param RequestContext $context Le contexte de la requête.
396
+ * @return array{behaviorScore: float}
396
397
  */
397
398
  public static function getBehaviorScore(RequestContext $context): array
398
399
  {
399
- $behaviorHeader = $context->getHeader('x-behavior-metrics');
400
- if (!$behaviorHeader) {
400
+ $header = $context->getHeader('x-behavior-metrics');
401
+ if (empty($header)) {
401
402
  return ['behaviorScore' => 0.0];
402
403
  }
403
404
 
404
- $metrics = json_decode($behaviorHeader, true);
405
- if (json_last_error() !== JSON_ERROR_NONE) {
406
- return ['behaviorScore' => 10.0]; // En-tête malformé
405
+ if ($header === '1' || $header === 1) {
406
+ return ['behaviorScore' => 0.0];
407
407
  }
408
-
409
- if (!empty($metrics['honeypotInteraction'])) {
408
+ if ($header === '0' || $header === 0) {
410
409
  return ['behaviorScore' => 100.0];
411
410
  }
412
411
 
413
- $score = 0.0;
414
- if (!empty($metrics['prototypeTampered'])) {
415
- $score += 80.0;
412
+ if (is_numeric($header)) {
413
+ $score = max(0.0, min(100.0, (float)$header));
414
+ return ['behaviorScore' => $score];
416
415
  }
417
416
 
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;
417
+ if (is_string($header)) {
418
+ $trimmed = trim($header);
419
+ if (is_numeric($trimmed)) {
420
+ if ($trimmed === '1') {
421
+ return ['behaviorScore' => 0.0];
422
+ }
423
+ if ($trimmed === '0') {
424
+ return ['behaviorScore' => 100.0];
425
+ }
426
+ $score = max(0.0, min(100.0, (float)$trimmed));
427
+ return ['behaviorScore' => $score];
429
428
  }
430
429
  }
431
430
 
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;
431
+ $metrics = json_decode($header, true);
432
+ if (json_last_error() !== JSON_ERROR_NONE) {
433
+ return ['behaviorScore' => 10.0];
434
+ }
435
+
436
+ if ($metrics === 1) {
437
+ return ['behaviorScore' => 0.0];
438
+ }
439
+ if ($metrics === 0) {
440
+ return ['behaviorScore' => 100.0];
437
441
  }
438
442
 
439
- if (($metrics['keystrokeLatency'] ?? 0) > 0 && $metrics['keystrokeLatency'] < 40) $score += 25;
440
- if (($metrics['keystrokeLatency'] ?? 0) > 1000) $score += 15;
443
+ if (is_numeric($metrics)) {
444
+ $score = max(0.0, min(100.0, (float)$metrics));
445
+ return ['behaviorScore' => $score];
446
+ }
441
447
 
442
- // NOUVEAU: Analyse de digraphie/trigraphie (dwell & flight times)
443
- $dwellTimes = $metrics['keystrokeDwellTimes'] ?? [];
444
- $flightTimes = $metrics['keystrokeFlightTimes'] ?? [];
448
+ if (!is_array($metrics)) {
449
+ return ['behaviorScore' => 0.0];
450
+ }
445
451
 
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);
452
+ if ($metrics['honeypotInteraction'] ?? false) {
453
+ return ['behaviorScore' => 100.0];
454
+ }
450
455
 
451
- if ($stdDevDwell < 2.0) {
452
- $score += 35.0;
453
- }
454
- if ($meanDwell < 15.0) {
455
- $score += 25.0;
456
- }
456
+ $score = 0.0;
457
+ if ($metrics['prototypeTampered'] ?? false) {
458
+ $score += 80.0;
457
459
  }
458
460
 
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);
461
+ $mouseHistory = $metrics['mouseMovementsHistory'] ?? [];
462
+ $touchHistory = $metrics['touchMovementsHistory'] ?? [];
464
463
 
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;
464
+ $mouseAnalysis = RequestUtils::analyzeMouseMovements($mouseHistory);
465
+ $touchAnalysis = RequestUtils::analyzeTouchMovements($touchHistory);
466
+
467
+ $mouseAvgSpeed = (float)($mouseAnalysis['avgSpeed'] ?? 0.0);
468
+ $touchAvgSpeed = (float)($touchAnalysis['avgSpeed'] ?? 0.0);
469
+ $keystrokeLatency = (float)($metrics['keystrokeLatency'] ?? 0.0);
470
+
471
+ if (isset($metrics['historyLength'])) {
472
+ $historyLength = (int)$metrics['historyLength'];
473
+ if ($historyLength === 1) $score += 15;
474
+ elseif ($historyLength >= 5) $score -= 20;
475
+ elseif ($historyLength >= 2) $score -= 10;
476
+ } else {
477
+ if ($mouseAvgSpeed === 0.0 && $touchAvgSpeed === 0.0 && $keystrokeLatency === 0.0) {
478
+ $noInteractionPenalty = 5.0;
479
+ if ($metrics['rendering']['offscreenAnom'] ?? false) {
480
+ $noInteractionPenalty += 40.0;
481
+ }
482
+ $score += $noInteractionPenalty;
474
483
  }
475
484
  }
476
485
 
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
- }
486
+ if ($mouseAvgSpeed > 0) {
487
+ if ($mouseAvgSpeed > 3.0) $score += 25;
488
+ if (($mouseAnalysis['avgAcceleration'] ?? 0.0) > 0.5) $score += 20;
489
+ if (($mouseAnalysis['straightness'] ?? 1.0) > 0.95) $score += 30;
490
+ if (($mouseAnalysis['pauses'] ?? 0) === 0 && count($mouseAnalysis['segments'] ?? []) > 20) $score += 15;
483
491
  }
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
492
 
493
- // Détection de l'émulation (pression et rayon de contact constants)
494
- if ($touch['avgPressure'] > 0 && $touch['pressureVariance'] == 0) {
495
- $score += 30;
493
+ if ($touchAvgSpeed > 0 || !empty($touchHistory)) {
494
+ if (count($touchHistory) >= 3) {
495
+ if (($touchAnalysis['pressureVariance'] ?? 1.0) < 0.0001) {
496
+ $score += 35.0;
497
+ }
498
+ if (($touchAnalysis['radiusVariance'] ?? 1.0) < 0.0001) {
499
+ $score += 25.0;
496
500
  }
497
- if ($touch['avgRadius'] > 0 && $touch['radiusVariance'] == 0) {
498
- $score += 30;
501
+ if (($touchAnalysis['straightness'] ?? 0.0) > 0.98) {
502
+ $score += 20.0;
499
503
  }
500
504
  }
501
- if (count($touch['segments']) > 10) {
502
- $benfordDev = Optimization::benfordTest($touch['segments']);
503
- if ($benfordDev > 0.18) {
504
- $score += 35;
505
+ }
506
+
507
+ $dwellTimes = $metrics['keystrokeDwellTimes'] ?? [];
508
+ if (is_array($dwellTimes) && count($dwellTimes) >= 3) {
509
+ $dwellNumeric = array_filter($dwellTimes, 'is_numeric');
510
+ if (count($dwellNumeric) >= 3) {
511
+ $meanDwell = array_sum($dwellNumeric) / count($dwellNumeric);
512
+ $dwellVar = array_reduce($dwellNumeric, fn($carry, $val) => $carry + pow((float)$val - $meanDwell, 2), 0.0) / count($dwellNumeric);
513
+ if ($dwellVar < 2.0) {
514
+ $score += 40.0;
505
515
  }
506
516
  }
517
+ }
507
518
 
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;
519
+ $flightTimes = $metrics['keystrokeFlightTimes'] ?? [];
520
+ if (is_array($flightTimes) && count($flightTimes) >= 3) {
521
+ $extractedFlightTimes = [];
522
+ foreach ($flightTimes as $ft) {
523
+ if (is_array($ft) && isset($ft['time']) && is_numeric($ft['time'])) {
524
+ $extractedFlightTimes[] = (float)$ft['time'];
525
+ } elseif (is_numeric($ft)) {
526
+ $extractedFlightTimes[] = (float)$ft;
527
+ }
528
+ }
529
+ if (count($extractedFlightTimes) >= 3) {
530
+ $meanFlight = array_sum($extractedFlightTimes) / count($extractedFlightTimes);
531
+ $flightVar = array_reduce($extractedFlightTimes, fn($carry, $val) => $carry + pow($val - $meanFlight, 2), 0.0) / count($extractedFlightTimes);
532
+ if ($flightVar < 2.0) {
533
+ $score += 40.0;
514
534
  }
515
535
  }
516
536
  }
@@ -518,6 +538,7 @@ class RequestUtils
518
538
  return ['behaviorScore' => min(100.0, $score)];
519
539
  }
520
540
 
541
+
521
542
  /**
522
543
  * Calcule un score basé sur la régularité d'affichage (V-Sync) et l'utilisation suspecte d'OffscreenCanvas.
523
544
  * @return array{'renderingAnomalyScore': float}
@@ -2008,6 +2029,21 @@ class RequestUtils
2008
2029
  return ['tcpAnomalyScore' => $tcpAnomalyScore];
2009
2030
  }
2010
2031
 
2032
+ /**
2033
+ * Helper to find the first key of a value from a list of needles in an array.
2034
+ * @param array<int, string> $haystack
2035
+ * @param array<int, string> $needles
2036
+ * @return int|null
2037
+ */
2038
+ private static function array_find_key(array $haystack, array $needles): ?int
2039
+ {
2040
+ foreach ($needles as $needle) {
2041
+ $key = array_search($needle, $haystack, true);
2042
+ if ($key !== false) return (int)$key;
2043
+ }
2044
+ return null;
2045
+ }
2046
+
2011
2047
  /**
2012
2048
  * Détecte les anomalies de protocole (HTTP/2 et QUIC/HTTP3) par rapport au User-Agent.
2013
2049
  * @param RequestContext $context
@@ -2022,51 +2058,95 @@ class RequestUtils
2022
2058
  $uaParts = self::parseUserAgent($ua);
2023
2059
  $browser = $uaParts['browser'] ?? null;
2024
2060
 
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;
2061
+ if ($browser) {
2062
+ // HTTP/2 Anomaly logic
2063
+ $h2Fp = $context->getHeader('x-http2-fingerprint') ?? $context->http2Fingerprint ?? null;
2064
+ if ($h2Fp && is_string($h2Fp)) {
2065
+ $parts = explode('|', $h2Fp);
2066
+ if (count($parts) >= 3) {
2067
+ $connWindow = (int)$parts[1];
2068
+ $streamPriority = $parts[2] ?? '';
2069
+ $headerOrder = $parts[3] ?? '';
2070
+ $isChromium = str_starts_with($browser, 'Chrome') || str_starts_with($browser, 'Edge');
2071
+ $isFirefox = str_starts_with($browser, 'Firefox');
2072
+ $isSafari = str_starts_with($browser, 'Safari');
2073
+
2074
+ if ($isChromium) {
2075
+ if ($headerOrder && $headerOrder !== 'm,a,s,p') $http2Anomaly += 60.0;
2076
+ if ($connWindow === 65535 || $connWindow === 65536) $http2Anomaly += 40.0;
2077
+ if ($streamPriority === '0' || $streamPriority === '') $http2Anomaly += 50.0;
2078
+ } elseif ($isFirefox) {
2079
+ if ($headerOrder && $headerOrder !== 'm,s,p,a') $http2Anomaly += 60.0;
2080
+ } elseif ($isSafari) {
2081
+ if ($headerOrder && $headerOrder !== 'm,s,p,a') $http2Anomaly += 60.0;
2082
+ }
2043
2083
  }
2044
2084
  }
2045
- }
2046
2085
 
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];
2086
+ // QUIC Anomaly logic
2087
+ $quicFp = $context->getHeader('x-quic-fp') ?? $context->quicFingerprint ?? null;
2088
+ if ($quicFp && is_string($quicFp)) {
2089
+ $parts = explode(';', $quicFp);
2090
+ if (count($parts) >= 2) {
2091
+ $params = [];
2092
+ foreach (explode(',', $parts[1]) as $p) {
2093
+ $kv = explode('=', $p, 2);
2094
+ if (count($kv) === 2) {
2095
+ $params[$kv[0]] = $kv[1];
2096
+ }
2097
+ }
2098
+ $priorityOrder = $parts[2] ?? '';
2099
+ $frameOrderRaw = $parts[3] ?? $context->getHeader('x-quic-frame-order') ?? '';
2100
+ $frameOrder = array_values(array_filter(array_map('trim', explode(',', strtolower($frameOrderRaw)))));
2101
+
2102
+ $isChromium = str_starts_with($browser, 'Chrome') || str_starts_with($browser, 'Edge');
2103
+ $isFirefox = str_starts_with($browser, 'Firefox');
2104
+ $isSafari = str_starts_with($browser, 'Safari');
2105
+
2106
+ $maxData = (int)($params['1'] ?? $params['0x01'] ?? 0);
2107
+ $maxStreams = (int)($params['4'] ?? $params['8'] ?? $params['0x08'] ?? 0);
2108
+ $bidiLocal = (int)($params['5'] ?? $params['0x05'] ?? 0);
2109
+ $bidiRemote = (int)($params['6'] ?? $params['0x06'] ?? 0);
2110
+
2111
+ if ($isChromium) {
2112
+ if ($maxData > 0 && $maxData < 1048576) $quicAnomaly += 40.0;
2113
+ if ($maxStreams > 0 && $maxStreams !== 100) $quicAnomaly += 30.0;
2114
+ if (!empty($priorityOrder) && !str_contains($priorityOrder, 'u=')) $quicAnomaly += 30.0;
2115
+ if ($bidiLocal > 0 && ($bidiLocal < 524288 || $bidiLocal === 262144)) $quicAnomaly += 40.0;
2116
+ if ($bidiRemote > 0 && ($bidiRemote < 524288 || $bidiRemote === 262144)) $quicAnomaly += 30.0;
2117
+
2118
+ if (count($frameOrder) >= 2) {
2119
+ $sIdx = self::array_find_key($frameOrder, ['s', 'settings', '4']);
2120
+ $mIdx = self::array_find_key($frameOrder, ['m', 'max_streams', '18']);
2121
+ $pIdx = self::array_find_key($frameOrder, ['p', 'priority', 'priority_update', '15']);
2122
+
2123
+ if ($sIdx !== null && $sIdx !== 0) $quicAnomaly += 50.0;
2124
+ if ($mIdx !== null && $sIdx !== null && $mIdx < $sIdx) $quicAnomaly += 60.0;
2125
+ if ($pIdx !== null && $sIdx !== null && $pIdx < $sIdx) $quicAnomaly += 60.0;
2126
+ }
2127
+ } elseif ($isFirefox) {
2128
+ if ($maxData > 0 && $maxData > 5000000) $quicAnomaly += 40.0;
2129
+ if ($maxStreams === 100) $quicAnomaly += 50.0;
2130
+ if ($bidiLocal === 6291456) $quicAnomaly += 50.0;
2131
+
2132
+ if (count($frameOrder) >= 2) {
2133
+ $sIdx = self::array_find_key($frameOrder, ['s', 'settings', '4']);
2134
+ if ($sIdx !== null && $sIdx !== 0) $quicAnomaly += 50.0;
2135
+ }
2136
+ } elseif ($isSafari) {
2137
+ if ($maxStreams === 100 && $maxData === 1572864 && str_contains($priorityOrder, 'u=2,i')) {
2138
+ $quicAnomaly += 60.0;
2139
+ }
2140
+ if ($bidiLocal === 6291456) $quicAnomaly += 50.0;
2141
+
2142
+ if (count($frameOrder) >= 2) {
2143
+ $sIdx = self::array_find_key($frameOrder, ['s', 'settings', '4']);
2144
+ $mIdx = self::array_find_key($frameOrder, ['m', 'max_streams']);
2145
+ if ($mIdx !== null && ($mIdx === 0 || ($sIdx !== null && $mIdx < $sIdx))) {
2146
+ $quicAnomaly += 50.0;
2147
+ }
2148
+ }
2057
2149
  }
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
2150
  }
2071
2151
  }
2072
2152
  }
@@ -2076,7 +2156,9 @@ class RequestUtils
2076
2156
  0.0,
2077
2157
  min(100.0, $http2Anomaly),
2078
2158
  min(100.0, $quicAnomaly)
2079
- )
2159
+ ),
2160
+ 'http2AnomalyScore' => min(100.0, $http2Anomaly),
2161
+ 'quicAnomalyScore' => min(100.0, $quicAnomaly)
2080
2162
  ];
2081
2163
  }
2082
2164
 
@@ -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.4
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.4\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.4\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.4
8
8
  License: MIT
9
9
  License URI: https://opensource.org/licenses/MIT
10
10