@anonympins/fingerprint 0.7.0 → 0.7.1

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 (61) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -1
  3. package/composer.json +8 -1
  4. package/package.json +1 -1
  5. package/public/fingerprint-wordpress.zip +0 -0
  6. package/src/js/build-client.js +8 -10
  7. package/src/js/fingerprint.builder.js +37 -38
  8. package/src/js/fingerprint.client.js +1531 -1463
  9. package/src/js/fingerprint.client.obfuscated.js +1 -0
  10. package/src/js/fingerprint.js +152 -10
  11. package/src/js/fingerprint.utils.js +213 -213
  12. package/src/js/library.js +340 -376
  13. package/src/js/mongodb-store.js +1 -1
  14. package/src/js/optimization.worker.js +27 -27
  15. package/src/js/pow.solver.inline.js +154 -56
  16. package/src/js/pow.solver.js +180 -92
  17. package/src/js/pow.worker.js +48 -48
  18. package/src/js/redis-store.js +1 -1
  19. package/src/js/tests/fingerprint.client.init.test.js +146 -143
  20. package/src/js/tests/fingerprint.test.js +34 -7
  21. package/src/js/tests/ip-reputation.test.js +1 -0
  22. package/src/js/upow-model-task.js +14 -15
  23. package/src/php/AutoTuner.php +34 -34
  24. package/src/php/Challenge/ChallengeUtils.php +56 -10
  25. package/src/php/Config/SecurityProfiles.php +20 -0
  26. package/src/php/DirectFingerprint.php +190 -145
  27. package/src/php/FingerprintBuilder.php +29 -30
  28. package/src/php/FingerprintClient.php +147 -148
  29. package/src/php/FingerprintEngine.php +7 -5
  30. package/src/php/Ja3AnomalyDetector.php +33 -32
  31. package/src/php/Optimization/FunctionRegistry.php +7 -7
  32. package/src/php/Optimization/Optimization.php +24 -24
  33. package/src/php/Optimization/OptimizationOperators.php +26 -25
  34. package/src/php/Optimization/ProblemInitializers.php +52 -52
  35. package/src/php/ProblemManager.php +31 -34
  36. package/src/php/RequestContext.php +8 -1
  37. package/src/php/Store/IStore.php +7 -8
  38. package/src/php/Store/InMemoryStore.php +66 -66
  39. package/src/php/Store/MongoDbStore.php +5 -5
  40. package/src/php/Store/RedisStore.php +5 -5
  41. package/src/php/Store/StoreManager.php +35 -35
  42. package/src/php/Tests/ChallengeUtilsTest.php +453 -453
  43. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  44. package/src/php/Tests/FingerprintEngineTest.php +662 -662
  45. package/src/php/Tests/IpReputationTest.php +10 -0
  46. package/src/php/Tests/MaliciousPatternsTest.php +103 -103
  47. package/src/php/Tests/MetricsTest.php +97 -97
  48. package/src/php/Tests/ProblemManagerTest.php +376 -376
  49. package/src/php/Tests/QuicFingerprintTest.php +53 -53
  50. package/src/php/Tests/RequestUtilsTest.php +471 -471
  51. package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
  52. package/src/php/Tests/config/ed25519_key.json +3 -3
  53. package/src/php/Utils/Env.php +49 -49
  54. package/src/php/Utils/MaliciousPatterns.php +74 -74
  55. package/src/php/Utils/MetricsManager.php +209 -209
  56. package/src/php/Utils/RequestUtils.php +129 -7
  57. package/src/php/Utils/TLSClientHelloParser.php +369 -369
  58. package/src/php/WordPress/WpDbStore.php +155 -0
  59. package/src/php/WordPress/fingerprint-wordpress.php +217 -0
  60. package/src/php/WordPress/package.php +123 -0
  61. package/src/php/bin/auto-tune.php +118 -118
@@ -1,149 +1,148 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint;
6
- use Anonympins\Fingerprint\Config\SecurityProfiles;
7
-
8
- /**
9
- * FingerprintClient - Wrapper PHP pour la bibliothèque de fingerprinting côté client.
10
- *
11
- * Cette classe facilite l'intégration de la bibliothèque JavaScript `fingerprint.client.js`
12
- * dans une application PHP. Elle gère l'injection sécurisée du script et la création
13
- * de "honeypots" (pièges à bots) dans les formulaires.
14
- */
15
- class FingerprintClient
16
- {
17
- /**
18
- * @var string Le chemin vers le fichier de la bibliothèque client JavaScript.
19
- */
20
- private string $clientScriptPath;
21
-
22
- /**
23
- * @var array La configuration à passer à la fonction `initializeClient` de la bibliothèque JS.
24
- */
25
- private array $clientConfig;
26
-
27
- /**
28
- * @var string|null Un nonce cryptographique pour la Content Security Policy (CSP).
29
- */
30
- private ?string $nonce;
31
-
32
- /**
33
- * Constructeur de la classe.
34
- *
35
- * @param string $clientScriptPath Le chemin d'accès web au fichier `fingerprint.client.js`.
36
- * @param array $clientConfig La configuration pour la bibliothèque client (souris, frappes, honeypots, etc.).
37
- */
38
- public function __construct(string $clientScriptPath, array $clientConfig = [])
39
- {
40
- $this->clientScriptPath = $clientScriptPath;
41
-
42
- $defaultConfig = [
43
- 'mouse' => true,
44
- 'keystrokes' => true,
45
- 'clicks' => true,
46
- 'honeypots' => [],
47
- 'fetch' => [
48
- 'handleChallenges' => true,
49
- 'probationaryTtl' => 30000, // 30 seconds
50
- ],
51
- 'wasm' => true, // Activer la tentative de chargement du module WASM
52
- 'wasmPath' => '/fp.js' // Chemin vers le script de chargement WASM
53
- ];
54
-
55
- // Utiliser une fusion profonde pour permettre de surcharger des sous-clés
56
- $this->clientConfig = SecurityProfiles::deepMerge($defaultConfig, $clientConfig);
57
-
58
- try {
59
- // Génère un nonce pour CSP si possible, pour une sécurité renforcée.
60
- $this->nonce = bin2hex(random_bytes(16));
61
- } catch (\Exception $e) {
62
- $this->nonce = null;
63
- }
64
- }
65
-
66
- /**
67
- * Génère un champ de formulaire "honeypot" caché.
68
- * Les bots le rempliront, mais il sera invisible pour les humains.
69
- *
70
- * @param string $fieldName Le nom du champ (doit correspondre à la configuration client).
71
- * @return string Le code HTML du champ honeypot.
72
- */
73
- public function generateHoneypotField(string $fieldName): string
74
- {
75
- // Ajoute le champ à la configuration pour que le script client le surveille.
76
- if (!in_array($fieldName, $this->clientConfig['honeypots'])) {
77
- $this->clientConfig['honeypots'][] = $fieldName;
78
- }
79
-
80
- // Styles CSS polymorphiques pour cacher le champ de manière robuste.
81
- $styleOptions = [
82
- 'position:absolute; left:-9999px; top:-9999px; transform:scale(0); opacity:0; pointer-events:none;',
83
- 'position:fixed; left:-8888px; top:-8888px; width:0; height:0; overflow:hidden; opacity:0; pointer-events:none;',
84
- 'display:none; visibility:hidden; pointer-events:none;'
85
- ];
86
- $styles = $styleOptions[array_rand($styleOptions)];
87
-
88
- $containerTags = ['div', 'span', 'p', 'section'];
89
- $tag = $containerTags[array_rand($containerTags)];
90
-
91
- $nestingType = rand(0, 1);
92
- if ($nestingType === 1) {
93
- return '<' . $tag . ' style="' . $styles . '" aria-hidden="true">'
94
- . '<label for="' . htmlspecialchars($fieldName) . '">' . htmlspecialchars($fieldName)
95
- . '<input type="text" id="' . htmlspecialchars($fieldName) . '" name="' . htmlspecialchars($fieldName) . '" tabindex="-1" autocomplete="off">'
96
- . '</label>'
97
- . '</' . $tag . '>';
98
- }
99
-
100
- return '<' . $tag . ' style="' . $styles . '" aria-hidden="true">'
101
- . '<label for="' . htmlspecialchars($fieldName) . '">' . htmlspecialchars($fieldName) . '</label>'
102
- . '<input type="text" id="' . htmlspecialchars($fieldName) . '" name="' . htmlspecialchars($fieldName) . '" tabindex="-1" autocomplete="off">'
103
- . '</' . $tag . '>';
104
- }
105
-
106
- /**
107
- * Génère le bloc de script complet à inclure dans une page HTML.
108
- *
109
- * @return string Le code HTML des balises <script>.
110
- */
111
- public function getScriptTag(): string
112
- {
113
- $configJson = json_encode($this->clientConfig);
114
- $nonceAttr = $this->nonce ? ' nonce="' . $this->nonce . '"' : '';
115
-
116
- // Le script d'initialisation qui sera inclus dans la page.
117
- $initScript = <<<JS
118
- document.addEventListener('DOMContentLoaded', function() {
119
- const config = {$configJson};
120
- if (window.ClientLibrary) {
121
- if (config.wasmPath) {
122
- const wasmScript = document.createElement('script');
123
- wasmScript.src = config.wasmPath;
124
- wasmScript.async = true;
125
- wasmScript.nonce = '{$this->nonce}';
126
- document.head.appendChild(wasmScript);
127
- }
128
-
129
- window.ClientLibrary.initializeClient(config);
130
- } else {
131
- console.error('Fingerprint client library not loaded.');
132
- }
133
- });
134
- JS;
135
-
136
- // On combine le chargement de la bibliothèque et le script d'initialisation.
137
- return '<script src="' . htmlspecialchars($this->clientScriptPath) . '"' . $nonceAttr . '></script>'
138
- . '<script' . $nonceAttr . '>' . $initScript . '</script>';
139
- }
140
-
141
- /**
142
- * Retourne le nonce généré pour pouvoir l'utiliser dans les en-têtes CSP.
143
- * @return string|null
144
- */
145
- public function getNonce(): ?string
146
- {
147
- return $this->nonce;
148
- }
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint;
6
+ use Anonympins\Fingerprint\Config\SecurityProfiles;
7
+
8
+ /**
9
+ * FingerprintClient - PHP wrapper for the client-side fingerprinting library.
10
+ *
11
+ * Handles script injection and generation of polymorphic honeypot fields
12
+ * inside HTML forms.
13
+ */
14
+ class FingerprintClient
15
+ {
16
+ /**
17
+ * @var string Path to client script asset.
18
+ */
19
+ private string $clientScriptPath;
20
+
21
+ /**
22
+ * @var array Configuration payload passed to `initializeClient`.
23
+ */
24
+ private array $clientConfig;
25
+
26
+ /**
27
+ * @var string|null Cryptographic nonce for Content Security Policy (CSP).
28
+ */
29
+ private ?string $nonce;
30
+
31
+ /**
32
+ * Constructor.
33
+ *
34
+ * @param string $clientScriptPath Web-accessible path to `fingerprint.client.js`.
35
+ * @param array $clientConfig Client initialization options.
36
+ */
37
+ public function __construct(string $clientScriptPath, array $clientConfig = [])
38
+ {
39
+ $this->clientScriptPath = $clientScriptPath;
40
+
41
+ $defaultConfig = [
42
+ 'mouse' => true,
43
+ 'keystrokes' => true,
44
+ 'clicks' => true,
45
+ 'honeypots' => [],
46
+ 'fetch' => [
47
+ 'handleChallenges' => true,
48
+ 'probationaryTtl' => 30000, // 30 seconds
49
+ ],
50
+ 'wasm' => true, // Attempt to load WASM module
51
+ 'wasmPath' => '/fp.js' // Path to WASM loader script
52
+ ];
53
+
54
+ // Deep merge configuration overrides
55
+ $this->clientConfig = SecurityProfiles::deepMerge($defaultConfig, $clientConfig);
56
+
57
+ try {
58
+ // Generate CSP nonce when possible
59
+ $this->nonce = bin2hex(random_bytes(16));
60
+ } catch (\Exception $e) {
61
+ $this->nonce = null;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Generates a hidden honeypot form field.
67
+ * Traps automated bots while remaining invisible to human users.
68
+ *
69
+ * @param string $fieldName Field name (must match client honeypot registration).
70
+ * @return string Generated HTML markup.
71
+ */
72
+ public function generateHoneypotField(string $fieldName): string
73
+ {
74
+ // Register field in client configuration for monitoring
75
+ if (!in_array($fieldName, $this->clientConfig['honeypots'])) {
76
+ $this->clientConfig['honeypots'][] = $fieldName;
77
+ }
78
+
79
+ // Polymorphic CSS styling to robustly obscure honeypots
80
+ $styleOptions = [
81
+ 'position:absolute; left:-9999px; top:-9999px; transform:scale(0); opacity:0; pointer-events:none;',
82
+ 'position:fixed; left:-8888px; top:-8888px; width:0; height:0; overflow:hidden; opacity:0; pointer-events:none;',
83
+ 'display:none; visibility:hidden; pointer-events:none;'
84
+ ];
85
+ $styles = $styleOptions[array_rand($styleOptions)];
86
+
87
+ $containerTags = ['div', 'span', 'p', 'section'];
88
+ $tag = $containerTags[array_rand($containerTags)];
89
+
90
+ $nestingType = rand(0, 1);
91
+ if ($nestingType === 1) {
92
+ return '<' . $tag . ' style="' . $styles . '" aria-hidden="true">'
93
+ . '<label for="' . htmlspecialchars($fieldName) . '">' . htmlspecialchars($fieldName)
94
+ . '<input type="text" id="' . htmlspecialchars($fieldName) . '" name="' . htmlspecialchars($fieldName) . '" tabindex="-1" autocomplete="off">'
95
+ . '</label>'
96
+ . '</' . $tag . '>';
97
+ }
98
+
99
+ return '<' . $tag . ' style="' . $styles . '" aria-hidden="true">'
100
+ . '<label for="' . htmlspecialchars($fieldName) . '">' . htmlspecialchars($fieldName) . '</label>'
101
+ . '<input type="text" id="' . htmlspecialchars($fieldName) . '" name="' . htmlspecialchars($fieldName) . '" tabindex="-1" autocomplete="off">'
102
+ . '</' . $tag . '>';
103
+ }
104
+
105
+ /**
106
+ * Generates HTML script tags to embed the client library on a page.
107
+ *
108
+ * @return string HTML <script> markup.
109
+ */
110
+ public function getScriptTag(): string
111
+ {
112
+ $configJson = json_encode($this->clientConfig);
113
+ $nonceAttr = $this->nonce ? ' nonce="' . $this->nonce . '"' : '';
114
+
115
+ // Inline initialization script embedded in HTML
116
+ $initScript = <<<JS
117
+ document.addEventListener('DOMContentLoaded', function() {
118
+ const config = {$configJson};
119
+ if (window.ClientLibrary) {
120
+ if (config.wasmPath) {
121
+ const wasmScript = document.createElement('script');
122
+ wasmScript.src = config.wasmPath;
123
+ wasmScript.async = true;
124
+ wasmScript.nonce = '{$this->nonce}';
125
+ document.head.appendChild(wasmScript);
126
+ }
127
+
128
+ window.ClientLibrary.initializeClient(config);
129
+ } else {
130
+ console.error('Fingerprint client library not loaded.');
131
+ }
132
+ });
133
+ JS;
134
+
135
+ // Combine library loader script and inline initialization
136
+ return '<script src="' . htmlspecialchars($this->clientScriptPath) . '"' . $nonceAttr . '></script>'
137
+ . '<script' . $nonceAttr . '>' . $initScript . '</script>';
138
+ }
139
+
140
+ /**
141
+ * Returns the generated CSP nonce.
142
+ * @return string|null
143
+ */
144
+ public function getNonce(): ?string
145
+ {
146
+ return $this->nonce;
147
+ }
149
148
  }
@@ -229,7 +229,8 @@
229
229
  'autotuning', 'enableUsefulWork', 'usefulWorkConfigPath', 'challengeNewDevices', 'graphql_operation_allowlist', 'dryRun',
230
230
  'similarityThreshold', 'summary', 'description',
231
231
  'ed25519_private_key', 'ed25519_public_key',
232
- 'wasm', 'enableProofOfSpace', 'pospace', 'federatedPeers', 'federationSecret', 'reset'
232
+ 'wasm', 'enableProofOfSpace', 'pospace', 'federatedPeers', 'federationSecret', 'reset',
233
+ 'filterWhitelist'
233
234
  ];
234
235
 
235
236
  if (empty($config['weights'])) {
@@ -562,7 +563,6 @@
562
563
  'options' => [
563
564
  'httponly' => true,
564
565
  'secure' => $secureOption,
565
- 'partitioned' => $secureOption,
566
566
  'samesite' => 'Strict',
567
567
  'path' => '/',
568
568
  ]
@@ -745,7 +745,7 @@
745
745
  $botnetCluster = RequestUtils::getBotnetClusterScore($context, $stableFpHash);
746
746
 
747
747
  // NOUVEAU: Score de réputation du sous-réseau IP
748
- $subnetScore = RequestUtils::getSubnetScore($context, $deviceId);
748
+ $subnetScore = RequestUtils::getSubnetScore($context, $deviceId, $this->securityConfig);
749
749
 
750
750
  // Score d'anomalie de pile TCP/IP
751
751
  $tcpAnomaly = RequestUtils::getTcpAnomalyScore($context);
@@ -1092,7 +1092,6 @@
1092
1092
  'samesite' => 'Strict',
1093
1093
  'expires' => time() + ($ticketTtl / 1000),
1094
1094
  'path' => '/',
1095
- 'partitioned' => $secureOption,
1096
1095
  ]
1097
1096
  ]
1098
1097
  ];
@@ -1399,6 +1398,7 @@
1399
1398
  $tlsSessionId = $context->tlsSessionId ?? '';
1400
1399
  $baseBlock = ChallengeUtils::createCpuChallengeBaseBlock($nonce, $clientSecret, $originalFingerprint, $context->clientIp, $tlsSessionId);
1401
1400
 
1401
+ $isHttps = !empty($context->isHttps);
1402
1402
  $challengeContext = [
1403
1403
  'clientSecret' => $clientSecret,
1404
1404
  'cpuTarget' => $cpuChallengeDetails['target'],
@@ -1407,6 +1407,7 @@
1407
1407
  'memDifficulty' => $memDifficulty,
1408
1408
  'baseBlock' => $baseBlock,
1409
1409
  'originalPath' => $context->path,
1410
+ 'isHttp' => !$isHttps,
1410
1411
  ];
1411
1412
 
1412
1413
  $store->set("secret:{$nonce}", $challengeContext, $this->securityConfig['challengeTtl'] ?? 300);
@@ -1441,7 +1442,8 @@
1441
1442
  // Pour les navigateurs, retourner une page HTML
1442
1443
  $pageBody = ChallengeUtils::generateCombinedPoWChallengePage(
1443
1444
  $cpuChallengeDetails, $memDifficulty, $clientSecret,
1444
- $this->securityConfig, $trapUrls, $originalFingerprint
1445
+ $this->securityConfig, $trapUrls, $originalFingerprint,
1446
+ $context->clientIp, $tlsSessionId, $baseBlock, $isHttps
1445
1447
  );
1446
1448
  $decision['body'] = $pageBody;
1447
1449
  }
@@ -5,17 +5,17 @@ declare(strict_types=1);
5
5
  namespace Anonympins\Fingerprint;
6
6
 
7
7
  /**
8
- * Classe de détection d'anomalies et d'usurpation TLS (JA3) en PHP.
8
+ * TLS (JA3) fingerprint anomaly and spoofing detector in PHP.
9
9
  */
10
10
  class Ja3AnomalyDetector
11
11
  {
12
- // Liste décimale des valeurs GREASE (RFC 8701) utilisées par les moteurs Chromium/Safari récents
12
+ // Decimal GREASE values (RFC 8701) used by Chromium and Safari TLS stacks
13
13
  private const GREASE_VALUES = [
14
14
  2570, 6682, 10794, 14906, 19018, 23130, 27242, 31354,
15
15
  35466, 39578, 43690, 47802, 51914, 55926, 60038, 64150
16
16
  ];
17
17
 
18
- // Base de données locale de signatures JA3 MD5 connues pour la corroboration de base
18
+ // Known JA3 MD5 signatures for baseline cross-layer verification
19
19
  private const TLS_FINGERPRINT_DB = [
20
20
  'e188a442b87f422c5a1e80b05399435b' => ['Chrome'],
21
21
  'd8e35855049321c6042a4325c697858f' => ['Chrome'],
@@ -29,7 +29,7 @@ class Ja3AnomalyDetector
29
29
  'b633f21d532d35967c8753c38536b4d3' => ['Safari'],
30
30
  '4d7a28d5f55b359b69100a311013f03e' => ['Safari', 'Chrome', 'Firefox'],
31
31
  '8dd3d7532873575314df23c447543001' => ['Safari', 'Chrome', 'Firefox'],
32
- // Bibliothèques et scrapers automatisés connus
32
+ // Automated scraper and library fingerprints
33
33
  '47344a349b75c4e82333475553b5f358' => ['Python'],
34
34
  'b29587b8a143c42546133ad7704b3310' => ['Go'],
35
35
  'd435b5223b2884c5a832b842637e245f' => ['Java'],
@@ -37,8 +37,8 @@ class Ja3AnomalyDetector
37
37
  ];
38
38
 
39
39
  /**
40
- * Analyse une chaîne JA3 brute non hachée.
41
- * Format attendu : "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
40
+ * Parses a raw JA3 string.
41
+ * Expected format: "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
42
42
  */
43
43
  public static function parseJa3(string $ja3String): ?array
44
44
  {
@@ -61,7 +61,7 @@ class Ja3AnomalyDetector
61
61
  }
62
62
 
63
63
  /**
64
- * Vérifie si un tableau contient au moins une valeur GREASE.
64
+ * Checks if a collection contains any GREASE value.
65
65
  */
66
66
  public static function hasGrease(array $values): bool
67
67
  {
@@ -74,7 +74,7 @@ class Ja3AnomalyDetector
74
74
  }
75
75
 
76
76
  /**
77
- * Parse sommairement le User-Agent pour en extraire la famille de navigateur.
77
+ * Extracts browser family from User-Agent string.
78
78
  */
79
79
  public static function getBrowserFamily(string $userAgent): ?string
80
80
  {
@@ -95,14 +95,14 @@ class Ja3AnomalyDetector
95
95
  }
96
96
 
97
97
  /**
98
- * Calcule le score global d'anomalie et d'usurpation JA3.
98
+ * Evaluates comprehensive JA3 spoofing and anomaly suspicion score.
99
99
  *
100
- * @param string|null $ja3Hash L'empreinte MD5 du JA3 (32 caractères)
101
- * @param string|null $ja3Raw L'empreinte brute non hachée (si disponible)
102
- * @param string $userAgent Le User-Agent de la requête
103
- * @param string $httpVersion La version HTTP de la requête (ex: "HTTP/2", "HTTP/1.1", ou "2.0")
104
- * @param object|null $cacheInstance Un driver de cache (ex: instance Redis) supportant get() et set() pour la détection de stagnation
105
- * @return int Un score de suspicion compris entre 0 et 100
100
+ * @param string|null $ja3Hash MD5 hash of JA3 fingerprint (32 hex characters).
101
+ * @param string|null $ja3Raw Raw unhashed JA3 string if available.
102
+ * @param string $userAgent Request User-Agent header.
103
+ * @param string $httpVersion HTTP protocol version string.
104
+ * @param object|null $cacheInstance Cache store for multi-UA stagnation detection.
105
+ * @return int Suspicion score between 0 and 100.
106
106
  */
107
107
  public static function getJa3AnomalyScore(
108
108
  ?string $ja3Hash,
@@ -115,18 +115,18 @@ class Ja3AnomalyDetector
115
115
  $claimedBrowser = self::getBrowserFamily($userAgent);
116
116
  $isHumanBrowser = in_array($claimedBrowser, ['Chrome', 'Firefox', 'Safari', 'Edge'], true);
117
117
 
118
- // --- ANALYSE 1 : CONTRÔLE SUR LE MD5 DU JA3 ---
118
+ // --- ANALYSIS 1: MD5 JA3 HASH VERIFICATION ---
119
119
  if ($ja3Hash && strlen($ja3Hash) === 32) {
120
120
  if (isset(self::TLS_FINGERPRINT_DB[$ja3Hash])) {
121
121
  $expectedBrowsers = self::TLS_FINGERPRINT_DB[$ja3Hash];
122
122
 
123
- // Cas A : L'empreinte correspond à un outil de scraping mais le UA prétend être humain
123
+ // Case A: TLS matches known automated library but UA claims standard browser
124
124
  $isLibrary = array_intersect($expectedBrowsers, ['Python', 'Go', 'Java', 'curl']);
125
125
  if (!empty($isLibrary) && $isHumanBrowser) {
126
- $score = max($score, 90); // Suspicion maximale : usurpation évidente
126
+ $score = max($score, 90); // High confidence spoofing
127
127
  }
128
128
 
129
- // Cas B : Incohérence directe entre le UA prétendu et la stack TLS correspondante
129
+ // Case B: Direct discrepancy between claimed browser and expected TLS stack
130
130
  if ($claimedBrowser !== null) {
131
131
  $matched = false;
132
132
  foreach ($expectedBrowsers as $expected) {
@@ -136,12 +136,12 @@ class Ja3AnomalyDetector
136
136
  }
137
137
  }
138
138
  if (!$matched) {
139
- $score = max($score, 80); // Le navigateur déclaré ne correspond pas au client TLS utilisé
139
+ $score = max($score, 80); // Browser family mismatch
140
140
  }
141
141
  }
142
142
  }
143
143
 
144
- // Cas C : Tracking de stagnation multi-UA (Stateful)
144
+ // Case C: Stateful multi-UA stagnation detection
145
145
  if ($cacheInstance && $claimedBrowser !== null && method_exists($cacheInstance, 'get') && method_exists($cacheInstance, 'set')) {
146
146
  $cacheKey = "ja3-browsers:" . $ja3Hash;
147
147
 
@@ -154,7 +154,7 @@ class Ja3AnomalyDetector
154
154
 
155
155
  if (!in_array($claimedBrowser, $seenBrowsers, true)) {
156
156
  $seenBrowsers[] = $claimedBrowser;
157
- // Cache pendant 24 heures (86400 secondes)
157
+ // Cache for 24 hours (86400 seconds)
158
158
  if (method_exists($cacheInstance, 'setex')) {
159
159
  $cacheInstance->setex($cacheKey, 86400, json_encode($seenBrowsers));
160
160
  } else {
@@ -162,32 +162,32 @@ class Ja3AnomalyDetector
162
162
  }
163
163
  }
164
164
 
165
- // Si une seule stack TLS génère des requêtes avec différents navigateurs, c'est un bot en rotation de UA
165
+ // Multiple rotating browser UAs emitted from identical TLS stack
166
166
  if (count($seenBrowsers) > 1) {
167
167
  $score = max($score, 85);
168
168
  }
169
169
  } catch (\Throwable $e) {
170
- // Tolérance aux pannes du cache
170
+ // Fault-tolerant fallback on cache error
171
171
  }
172
172
  }
173
173
  }
174
174
 
175
- // --- ANALYSE 2 : CONTRÔLE PROFOND SUR L'EMPREINTE BRUTE (RAW JA3) ---
175
+ // --- ANALYSIS 2: DEEP INSPECTION ON RAW JA3 STRING ---
176
176
  if ($ja3Raw) {
177
177
  $parsed = self::parseJa3($ja3Raw);
178
178
  if ($parsed) {
179
- // Contrôle A : Mécanisme GREASE pour Chrome / Edge (obligatoire)
179
+ // Check A: GREASE presence for Chrome / Edge
180
180
  if ($claimedBrowser === 'Chrome' || $claimedBrowser === 'Edge') {
181
181
  $hasCiphersGrease = self::hasGrease($parsed['ciphers']);
182
182
  $hasExtensionsGrease = self::hasGrease($parsed['extensions']);
183
183
 
184
184
  if (!$hasCiphersGrease && !$hasExtensionsGrease) {
185
- // Chrome ou Edge moderne sans GREASE = spoofing de bas niveau (ex: python-requests déguisé)
185
+ // Modern Chromium missing GREASE indicates spoofing
186
186
  $score = max($score, 75);
187
187
  }
188
188
  }
189
189
 
190
- // Contrôle B : HTTP/2 ou HTTP/3 sans négociation ALPN (Extension 16)
190
+ // Check B: HTTP/2 or HTTP/3 without ALPN extension (Extension 16)
191
191
  $isH2OrHigher = (
192
192
  strpos($httpVersion, '2.0') !== false ||
193
193
  strpos($httpVersion, 'HTTP/2') !== false ||
@@ -196,11 +196,11 @@ class Ja3AnomalyDetector
196
196
  $hasAlpnExtension = in_array(16, $parsed['extensions'], true);
197
197
 
198
198
  if ($isH2OrHigher && !$hasAlpnExtension) {
199
- // Négociation HTTP/2 active au niveau serveur mais absente au niveau des extensions TLS du client
199
+ // HTTP/2 active at server level but missing from client ALPN extension
200
200
  $score = max($score, 70);
201
201
  }
202
202
 
203
- // Contrôle C : Version TLS obsolète négociée par un navigateur moderne (ex: TLS < 1.2, id < 771)
203
+ // Check C: Deprecated TLS version negotiated by claimed modern browser
204
204
  if ($isHumanBrowser && $parsed['tlsVersion'] < 771) {
205
205
  $score = max($score, 80);
206
206
  }
@@ -211,8 +211,9 @@ class Ja3AnomalyDetector
211
211
  }
212
212
 
213
213
  /**
214
- * Feature 7 : Corrélation RTT & Latence de Proxy Résidentiel
215
- * Calcule un score d'anomalie en corrélant le RTT TCP de bas niveau avec la latence applicative.
214
+ * Correlates low-level TCP RTT with application layer latency to detect residential proxy hops.
215
+ * @param RequestContext $context
216
+ * @return int Suspicion score
216
217
  */
217
218
  public static function getRttProxyScore(RequestContext $context): int
218
219
  {
@@ -5,7 +5,7 @@ declare(strict_types=1);
5
5
  namespace Anonympins\Fingerprint\Optimization;
6
6
 
7
7
  /**
8
- * Registre pour exposer de manière contrôlée les fonctions de la bibliothèque d'optimisation.
8
+ * Registry to safely expose optimization library routines to task runners.
9
9
  */
10
10
  class FunctionRegistry
11
11
  {
@@ -13,17 +13,17 @@ class FunctionRegistry
13
13
  private static array $functions = [];
14
14
 
15
15
  /**
16
- * Initialise le registre avec les fonctions disponibles.
16
+ * Initializes registry with built-in solvers and evaluators.
17
17
  */
18
18
  private static function initialize(): void
19
19
  {
20
20
  if (empty(self::$functions)) {
21
- // Fonctions de "Scoring"
21
+ // Evaluators & Scoring Adapters
22
22
  self::$functions['tsp.calculateEnergy'] = [OptimizationUtils::class, 'evaluatePathDistance'];
23
23
  self::$functions['portfolio.calculateMetrics'] = [OptimizationOperators::class, 'calculatePortfolioMetrics'];
24
24
  self::$functions['facility.calculateEnergy'] = [OptimizationOperators::class, 'evaluateFacilityLocation'];
25
25
 
26
- // Fonctions de "Résolution"
26
+ // Full Optimization Solvers
27
27
  self::$functions['tsp.solve'] = [OptimizationOperators::class, 'solveTSP'];
28
28
  self::$functions['portfolio.solve'] = [OptimizationOperators::class, 'solvePortfolio'];
29
29
  self::$functions['fraud.solve'] = [OptimizationOperators::class, 'solveFraudDetection'];
@@ -33,7 +33,7 @@ class FunctionRegistry
33
33
  }
34
34
 
35
35
  /**
36
- * Récupère une fonction depuis le registre.
36
+ * Retrieves a function from the registry.
37
37
  */
38
38
  public static function get(string $name): ?callable
39
39
  {
@@ -41,7 +41,7 @@ class FunctionRegistry
41
41
  return self::$functions[$name] ?? null;
42
42
  }
43
43
  /**
44
- * Enregistre une nouvelle fonction. Principalement pour les tests.
44
+ * Registers a custom function. Primarily intended for testing.
45
45
  * @internal
46
46
  * @param string $name
47
47
  * @param callable $function
@@ -54,7 +54,7 @@ class FunctionRegistry
54
54
  }
55
55
 
56
56
  /**
57
- * Réinitialise le registre. Uniquement pour les tests.
57
+ * Resets function registry. Intended for testing.
58
58
  * @internal
59
59
  */
60
60
  public static function __internal_resetRegistry(): void