@anonympins/fingerprint 0.6.7 → 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 (63) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +3 -7
  3. package/composer.json +8 -1
  4. package/package.json +1 -1
  5. package/public/fingerprint-wordpress.zip +0 -0
  6. package/public/fp.wasm +0 -0
  7. package/src/js/build-client.js +9 -11
  8. package/src/js/dynamic-wasm.js +7 -7
  9. package/src/js/fingerprint.builder.js +37 -38
  10. package/src/js/fingerprint.client.js +236 -152
  11. package/src/js/fingerprint.client.obfuscated.js +1 -1
  12. package/src/js/fingerprint.js +517 -101
  13. package/src/js/fingerprint.utils.js +213 -213
  14. package/src/js/library.js +355 -384
  15. package/src/js/mongodb-store.js +1 -1
  16. package/src/js/optimization.worker.js +27 -27
  17. package/src/js/pow.solver.inline.js +206 -62
  18. package/src/js/pow.solver.js +235 -101
  19. package/src/js/pow.worker.js +48 -26
  20. package/src/js/redis-store.js +1 -1
  21. package/src/js/tests/fingerprint.client.init.test.js +146 -143
  22. package/src/js/tests/fingerprint.test.js +101 -40
  23. package/src/js/tests/ip-reputation.test.js +1 -0
  24. package/src/js/upow-model-task.js +14 -15
  25. package/src/php/AutoTuner.php +34 -34
  26. package/src/php/Challenge/ChallengeUtils.php +204 -32
  27. package/src/php/Config/SecurityProfiles.php +21 -0
  28. package/src/php/DirectFingerprint.php +190 -145
  29. package/src/php/FingerprintBuilder.php +29 -30
  30. package/src/php/FingerprintClient.php +24 -25
  31. package/src/php/FingerprintEngine.php +1836 -1880
  32. package/src/php/Ja3AnomalyDetector.php +258 -227
  33. package/src/php/Optimization/FunctionRegistry.php +7 -7
  34. package/src/php/Optimization/Optimization.php +24 -24
  35. package/src/php/Optimization/OptimizationOperators.php +28 -27
  36. package/src/php/Optimization/ProblemInitializers.php +52 -52
  37. package/src/php/ProblemManager.php +31 -34
  38. package/src/php/RequestContext.php +8 -1
  39. package/src/php/Store/IStore.php +7 -8
  40. package/src/php/Store/InMemoryStore.php +66 -66
  41. package/src/php/Store/MongoDbStore.php +5 -5
  42. package/src/php/Store/RedisStore.php +5 -5
  43. package/src/php/Store/StoreManager.php +35 -35
  44. package/src/php/Tests/ChallengeUtilsTest.php +453 -453
  45. package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
  46. package/src/php/Tests/FingerprintEngineTest.php +662 -693
  47. package/src/php/Tests/IpReputationTest.php +10 -0
  48. package/src/php/Tests/MaliciousPatternsTest.php +103 -103
  49. package/src/php/Tests/MetricsTest.php +97 -97
  50. package/src/php/Tests/ProblemManagerTest.php +376 -376
  51. package/src/php/Tests/QuicFingerprintTest.php +53 -53
  52. package/src/php/Tests/RequestUtilsTest.php +471 -471
  53. package/src/php/Tests/TLSClientHelloParserTest.php +177 -177
  54. package/src/php/Tests/config/ed25519_key.json +3 -3
  55. package/src/php/Utils/Env.php +49 -49
  56. package/src/php/Utils/MaliciousPatterns.php +74 -74
  57. package/src/php/Utils/MetricsManager.php +209 -209
  58. package/src/php/Utils/RequestUtils.php +236 -19
  59. package/src/php/Utils/TLSClientHelloParser.php +369 -212
  60. package/src/php/WordPress/WpDbStore.php +155 -0
  61. package/src/php/WordPress/fingerprint-wordpress.php +217 -0
  62. package/src/php/WordPress/package.php +123 -0
  63. package/src/php/bin/auto-tune.php +118 -118
@@ -1,228 +1,259 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- namespace Anonympins\Fingerprint;
6
-
7
- /**
8
- * Classe de détection d'anomalies et d'usurpation TLS (JA3) en PHP.
9
- */
10
- class Ja3AnomalyDetector
11
- {
12
- // Liste décimale des valeurs GREASE (RFC 8701) utilisées par les moteurs Chromium/Safari récents
13
- private const GREASE_VALUES = [
14
- 2570, 6682, 10794, 14906, 19018, 23130, 27242, 31354,
15
- 35466, 39578, 43690, 47802, 51914, 55926, 60038, 64150
16
- ];
17
-
18
- // Base de données locale de signatures JA3 MD5 connues pour la corroboration de base
19
- private const TLS_FINGERPRINT_DB = [
20
- 'e188a442b87f422c5a1e80b05399435b' => ['Chrome'],
21
- 'd8e35855049321c6042a4325c697858f' => ['Chrome'],
22
- 'a9f90958d44533748c139a5d1895b925' => ['Chrome'],
23
- '3b5379916d2b3882253c42885956a350' => ['Chrome'],
24
- '59822058c95c33d2d06e52f410855c8c' => ['Chrome'],
25
- 'b386946a5a586163c7c533636b45c355' => ['Firefox'],
26
- '66236495a523c1785f8f3a105b248b11' => ['Firefox'],
27
- 'b73d470006575b5e35167a0b5a8540e2' => ['Firefox'],
28
- '8443d7562933834333943465d52363cf' => ['Firefox'],
29
- 'b633f21d532d35967c8753c38536b4d3' => ['Safari'],
30
- '4d7a28d5f55b359b69100a311013f03e' => ['Safari', 'Chrome', 'Firefox'],
31
- '8dd3d7532873575314df23c447543001' => ['Safari', 'Chrome', 'Firefox'],
32
- // Bibliothèques et scrapers automatisés connus
33
- '47344a349b75c4e82333475553b5f358' => ['Python'],
34
- 'b29587b8a143c42546133ad7704b3310' => ['Go'],
35
- 'd435b5223b2884c5a832b842637e245f' => ['Java'],
36
- 'c72366b9551263d990b7fa574225332c' => ['curl'],
37
- ];
38
-
39
- /**
40
- * Analyse une chaîne JA3 brute non hachée.
41
- * Format attendu : "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
42
- */
43
- public static function parseJa3(string $ja3String): ?array
44
- {
45
- if (empty($ja3String)) {
46
- return null;
47
- }
48
-
49
- $parts = explode(',', $ja3String);
50
- if (count($parts) !== 5) {
51
- return null;
52
- }
53
-
54
- return [
55
- 'tlsVersion' => (int)$parts[0],
56
- 'ciphers' => $parts[1] !== '' ? array_map('intval', explode('-', $parts[1])) : [],
57
- 'extensions' => $parts[2] !== '' ? array_map('intval', explode('-', $parts[2])) : [],
58
- 'curves' => $parts[3] !== '' ? array_map('intval', explode('-', $parts[3])) : [],
59
- 'points' => $parts[4] !== '' ? array_map('intval', explode('-', $parts[4])) : []
60
- ];
61
- }
62
-
63
- /**
64
- * Vérifie si un tableau contient au moins une valeur GREASE.
65
- */
66
- public static function hasGrease(array $values): bool
67
- {
68
- foreach ($values as $val) {
69
- if (in_array($val, self::GREASE_VALUES, true)) {
70
- return true;
71
- }
72
- }
73
- return false;
74
- }
75
-
76
- /**
77
- * Parse sommairement le User-Agent pour en extraire la famille de navigateur.
78
- */
79
- public static function getBrowserFamily(string $userAgent): ?string
80
- {
81
- $ua = strtolower($userAgent);
82
- if (strpos($ua, 'edg') !== false) {
83
- return 'Edge';
84
- }
85
- if (strpos($ua, 'chrome') !== false) {
86
- return 'Chrome';
87
- }
88
- if (strpos($ua, 'firefox') !== false) {
89
- return 'Firefox';
90
- }
91
- if (strpos($ua, 'safari') !== false) {
92
- return 'Safari';
93
- }
94
- return null;
95
- }
96
-
97
- /**
98
- * Calcule le score global d'anomalie et d'usurpation JA3.
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
106
- */
107
- public static function getJa3AnomalyScore(
108
- ?string $ja3Hash,
109
- ?string $ja3Raw,
110
- string $userAgent,
111
- string $httpVersion,
112
- ?object $cacheInstance = null
113
- ): int {
114
- $score = 0;
115
- $claimedBrowser = self::getBrowserFamily($userAgent);
116
- $isHumanBrowser = in_array($claimedBrowser, ['Chrome', 'Firefox', 'Safari', 'Edge'], true);
117
-
118
- // --- ANALYSE 1 : CONTRÔLE SUR LE MD5 DU JA3 ---
119
- if ($ja3Hash && strlen($ja3Hash) === 32) {
120
- if (isset(self::TLS_FINGERPRINT_DB[$ja3Hash])) {
121
- $expectedBrowsers = self::TLS_FINGERPRINT_DB[$ja3Hash];
122
-
123
- // Cas A : L'empreinte correspond à un outil de scraping mais le UA prétend être humain
124
- $isLibrary = array_intersect($expectedBrowsers, ['Python', 'Go', 'Java', 'curl']);
125
- if (!empty($isLibrary) && $isHumanBrowser) {
126
- $score = max($score, 90); // Suspicion maximale : usurpation évidente
127
- }
128
-
129
- // Cas B : Incohérence directe entre le UA prétendu et la stack TLS correspondante
130
- if ($claimedBrowser !== null) {
131
- $matched = false;
132
- foreach ($expectedBrowsers as $expected) {
133
- if (stripos($claimedBrowser, $expected) === 0) {
134
- $matched = true;
135
- break;
136
- }
137
- }
138
- if (!$matched) {
139
- $score = max($score, 80); // Le navigateur déclaré ne correspond pas au client TLS utilisé
140
- }
141
- }
142
- }
143
-
144
- // Cas C : Tracking de stagnation multi-UA (Stateful)
145
- if ($cacheInstance && $claimedBrowser !== null && method_exists($cacheInstance, 'get') && method_exists($cacheInstance, 'set')) {
146
- $cacheKey = "ja3-browsers:" . $ja3Hash;
147
-
148
- try {
149
- $rawCached = $cacheInstance->get($cacheKey);
150
- $seenBrowsers = $rawCached ? json_decode((string)$rawCached, true) : [];
151
- if (!is_array($seenBrowsers)) {
152
- $seenBrowsers = [];
153
- }
154
-
155
- if (!in_array($claimedBrowser, $seenBrowsers, true)) {
156
- $seenBrowsers[] = $claimedBrowser;
157
- // Cache pendant 24 heures (86400 secondes)
158
- if (method_exists($cacheInstance, 'setex')) {
159
- $cacheInstance->setex($cacheKey, 86400, json_encode($seenBrowsers));
160
- } else {
161
- $cacheInstance->set($cacheKey, json_encode($seenBrowsers), 86400);
162
- }
163
- }
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
166
- if (count($seenBrowsers) > 1) {
167
- $score = max($score, 85);
168
- }
169
- } catch (\Throwable $e) {
170
- // Tolérance aux pannes du cache
171
- }
172
- }
173
- }
174
-
175
- // --- ANALYSE 2 : CONTRÔLE PROFOND SUR L'EMPREINTE BRUTE (RAW JA3) ---
176
- if ($ja3Raw) {
177
- $parsed = self::parseJa3($ja3Raw);
178
- if ($parsed) {
179
- // Contrôle A : Mécanisme GREASE pour Chrome / Edge (obligatoire)
180
- if ($claimedBrowser === 'Chrome' || $claimedBrowser === 'Edge') {
181
- $hasCiphersGrease = self::hasGrease($parsed['ciphers']);
182
- $hasExtensionsGrease = self::hasGrease($parsed['extensions']);
183
-
184
- if (!$hasCiphersGrease && !$hasExtensionsGrease) {
185
- // Chrome ou Edge moderne sans GREASE = spoofing de bas niveau (ex: python-requests déguisé)
186
- $score = max($score, 75);
187
- }
188
- }
189
-
190
- // Contrôle B : HTTP/2 ou HTTP/3 sans négociation ALPN (Extension 16)
191
- $isH2OrHigher = (
192
- strpos($httpVersion, '2.0') !== false ||
193
- strpos($httpVersion, 'HTTP/2') !== false ||
194
- strpos($httpVersion, 'HTTP/3') !== false
195
- );
196
- $hasAlpnExtension = in_array(16, $parsed['extensions'], true);
197
-
198
- if ($isH2OrHigher && !$hasAlpnExtension) {
199
- // Négociation HTTP/2 active au niveau serveur mais absente au niveau des extensions TLS du client
200
- $score = max($score, 70);
201
- }
202
-
203
- // Contrôle C : Version TLS obsolète négociée par un navigateur moderne (ex: TLS < 1.2, id < 771)
204
- if ($isHumanBrowser && $parsed['tlsVersion'] < 771) {
205
- $score = max($score, 80);
206
- }
207
- }
208
- }
209
-
210
- return $score;
211
- }
212
- }
213
-
214
- // --- EXEMPLE D'UTILISATION PRATIQUE ---
215
- /*
216
- $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
217
- $httpVersion = $_SERVER['SERVER_PROTOCOL'] ?? '';
218
-
219
- // Récupération des en-têtes injectés par votre Reverse-Proxy (Nginx, HAProxy, etc.)
220
- $ja3Hash = $_SERVER['HTTP_X_JA3_HASH'] ?? null;
221
- $ja3Raw = $_SERVER['HTTP_X_JA3_RAW'] ?? null;
222
-
223
- // Redis facultatif pour la détection stateful de rotation UA
224
- $redis = new \Redis();
225
- $redis->connect('127.0.0.1', 6379);
226
-
227
- $suspicionScore = Ja3AnomalyDetector::getJa3AnomalyScore($ja3Hash, $ja3Raw, $userAgent, $httpVersion, $redis);
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ namespace Anonympins\Fingerprint;
6
+
7
+ /**
8
+ * TLS (JA3) fingerprint anomaly and spoofing detector in PHP.
9
+ */
10
+ class Ja3AnomalyDetector
11
+ {
12
+ // Decimal GREASE values (RFC 8701) used by Chromium and Safari TLS stacks
13
+ private const GREASE_VALUES = [
14
+ 2570, 6682, 10794, 14906, 19018, 23130, 27242, 31354,
15
+ 35466, 39578, 43690, 47802, 51914, 55926, 60038, 64150
16
+ ];
17
+
18
+ // Known JA3 MD5 signatures for baseline cross-layer verification
19
+ private const TLS_FINGERPRINT_DB = [
20
+ 'e188a442b87f422c5a1e80b05399435b' => ['Chrome'],
21
+ 'd8e35855049321c6042a4325c697858f' => ['Chrome'],
22
+ 'a9f90958d44533748c139a5d1895b925' => ['Chrome'],
23
+ '3b5379916d2b3882253c42885956a350' => ['Chrome'],
24
+ '59822058c95c33d2d06e52f410855c8c' => ['Chrome'],
25
+ 'b386946a5a586163c7c533636b45c355' => ['Firefox'],
26
+ '66236495a523c1785f8f3a105b248b11' => ['Firefox'],
27
+ 'b73d470006575b5e35167a0b5a8540e2' => ['Firefox'],
28
+ '8443d7562933834333943465d52363cf' => ['Firefox'],
29
+ 'b633f21d532d35967c8753c38536b4d3' => ['Safari'],
30
+ '4d7a28d5f55b359b69100a311013f03e' => ['Safari', 'Chrome', 'Firefox'],
31
+ '8dd3d7532873575314df23c447543001' => ['Safari', 'Chrome', 'Firefox'],
32
+ // Automated scraper and library fingerprints
33
+ '47344a349b75c4e82333475553b5f358' => ['Python'],
34
+ 'b29587b8a143c42546133ad7704b3310' => ['Go'],
35
+ 'd435b5223b2884c5a832b842637e245f' => ['Java'],
36
+ 'c72366b9551263d990b7fa574225332c' => ['curl'],
37
+ ];
38
+
39
+ /**
40
+ * Parses a raw JA3 string.
41
+ * Expected format: "TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurveFormats"
42
+ */
43
+ public static function parseJa3(string $ja3String): ?array
44
+ {
45
+ if (empty($ja3String)) {
46
+ return null;
47
+ }
48
+
49
+ $parts = explode(',', $ja3String);
50
+ if (count($parts) !== 5) {
51
+ return null;
52
+ }
53
+
54
+ return [
55
+ 'tlsVersion' => (int)$parts[0],
56
+ 'ciphers' => $parts[1] !== '' ? array_map('intval', explode('-', $parts[1])) : [],
57
+ 'extensions' => $parts[2] !== '' ? array_map('intval', explode('-', $parts[2])) : [],
58
+ 'curves' => $parts[3] !== '' ? array_map('intval', explode('-', $parts[3])) : [],
59
+ 'points' => $parts[4] !== '' ? array_map('intval', explode('-', $parts[4])) : []
60
+ ];
61
+ }
62
+
63
+ /**
64
+ * Checks if a collection contains any GREASE value.
65
+ */
66
+ public static function hasGrease(array $values): bool
67
+ {
68
+ foreach ($values as $val) {
69
+ if (in_array($val, self::GREASE_VALUES, true)) {
70
+ return true;
71
+ }
72
+ }
73
+ return false;
74
+ }
75
+
76
+ /**
77
+ * Extracts browser family from User-Agent string.
78
+ */
79
+ public static function getBrowserFamily(string $userAgent): ?string
80
+ {
81
+ $ua = strtolower($userAgent);
82
+ if (strpos($ua, 'edg') !== false) {
83
+ return 'Edge';
84
+ }
85
+ if (strpos($ua, 'chrome') !== false) {
86
+ return 'Chrome';
87
+ }
88
+ if (strpos($ua, 'firefox') !== false) {
89
+ return 'Firefox';
90
+ }
91
+ if (strpos($ua, 'safari') !== false) {
92
+ return 'Safari';
93
+ }
94
+ return null;
95
+ }
96
+
97
+ /**
98
+ * Evaluates comprehensive JA3 spoofing and anomaly suspicion score.
99
+ *
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
+ */
107
+ public static function getJa3AnomalyScore(
108
+ ?string $ja3Hash,
109
+ ?string $ja3Raw,
110
+ string $userAgent,
111
+ string $httpVersion,
112
+ ?object $cacheInstance = null
113
+ ): int {
114
+ $score = 0;
115
+ $claimedBrowser = self::getBrowserFamily($userAgent);
116
+ $isHumanBrowser = in_array($claimedBrowser, ['Chrome', 'Firefox', 'Safari', 'Edge'], true);
117
+
118
+ // --- ANALYSIS 1: MD5 JA3 HASH VERIFICATION ---
119
+ if ($ja3Hash && strlen($ja3Hash) === 32) {
120
+ if (isset(self::TLS_FINGERPRINT_DB[$ja3Hash])) {
121
+ $expectedBrowsers = self::TLS_FINGERPRINT_DB[$ja3Hash];
122
+
123
+ // Case A: TLS matches known automated library but UA claims standard browser
124
+ $isLibrary = array_intersect($expectedBrowsers, ['Python', 'Go', 'Java', 'curl']);
125
+ if (!empty($isLibrary) && $isHumanBrowser) {
126
+ $score = max($score, 90); // High confidence spoofing
127
+ }
128
+
129
+ // Case B: Direct discrepancy between claimed browser and expected TLS stack
130
+ if ($claimedBrowser !== null) {
131
+ $matched = false;
132
+ foreach ($expectedBrowsers as $expected) {
133
+ if (stripos($claimedBrowser, $expected) === 0) {
134
+ $matched = true;
135
+ break;
136
+ }
137
+ }
138
+ if (!$matched) {
139
+ $score = max($score, 80); // Browser family mismatch
140
+ }
141
+ }
142
+ }
143
+
144
+ // Case C: Stateful multi-UA stagnation detection
145
+ if ($cacheInstance && $claimedBrowser !== null && method_exists($cacheInstance, 'get') && method_exists($cacheInstance, 'set')) {
146
+ $cacheKey = "ja3-browsers:" . $ja3Hash;
147
+
148
+ try {
149
+ $rawCached = $cacheInstance->get($cacheKey);
150
+ $seenBrowsers = $rawCached ? json_decode((string)$rawCached, true) : [];
151
+ if (!is_array($seenBrowsers)) {
152
+ $seenBrowsers = [];
153
+ }
154
+
155
+ if (!in_array($claimedBrowser, $seenBrowsers, true)) {
156
+ $seenBrowsers[] = $claimedBrowser;
157
+ // Cache for 24 hours (86400 seconds)
158
+ if (method_exists($cacheInstance, 'setex')) {
159
+ $cacheInstance->setex($cacheKey, 86400, json_encode($seenBrowsers));
160
+ } else {
161
+ $cacheInstance->set($cacheKey, json_encode($seenBrowsers), 86400);
162
+ }
163
+ }
164
+
165
+ // Multiple rotating browser UAs emitted from identical TLS stack
166
+ if (count($seenBrowsers) > 1) {
167
+ $score = max($score, 85);
168
+ }
169
+ } catch (\Throwable $e) {
170
+ // Fault-tolerant fallback on cache error
171
+ }
172
+ }
173
+ }
174
+
175
+ // --- ANALYSIS 2: DEEP INSPECTION ON RAW JA3 STRING ---
176
+ if ($ja3Raw) {
177
+ $parsed = self::parseJa3($ja3Raw);
178
+ if ($parsed) {
179
+ // Check A: GREASE presence for Chrome / Edge
180
+ if ($claimedBrowser === 'Chrome' || $claimedBrowser === 'Edge') {
181
+ $hasCiphersGrease = self::hasGrease($parsed['ciphers']);
182
+ $hasExtensionsGrease = self::hasGrease($parsed['extensions']);
183
+
184
+ if (!$hasCiphersGrease && !$hasExtensionsGrease) {
185
+ // Modern Chromium missing GREASE indicates spoofing
186
+ $score = max($score, 75);
187
+ }
188
+ }
189
+
190
+ // Check B: HTTP/2 or HTTP/3 without ALPN extension (Extension 16)
191
+ $isH2OrHigher = (
192
+ strpos($httpVersion, '2.0') !== false ||
193
+ strpos($httpVersion, 'HTTP/2') !== false ||
194
+ strpos($httpVersion, 'HTTP/3') !== false
195
+ );
196
+ $hasAlpnExtension = in_array(16, $parsed['extensions'], true);
197
+
198
+ if ($isH2OrHigher && !$hasAlpnExtension) {
199
+ // HTTP/2 active at server level but missing from client ALPN extension
200
+ $score = max($score, 70);
201
+ }
202
+
203
+ // Check C: Deprecated TLS version negotiated by claimed modern browser
204
+ if ($isHumanBrowser && $parsed['tlsVersion'] < 771) {
205
+ $score = max($score, 80);
206
+ }
207
+ }
208
+ }
209
+
210
+ return $score;
211
+ }
212
+
213
+ /**
214
+ * Correlates low-level TCP RTT with application layer latency to detect residential proxy hops.
215
+ * @param RequestContext $context
216
+ * @return int Suspicion score
217
+ */
218
+ public static function getRttProxyScore(RequestContext $context): int
219
+ {
220
+ $tcpRttHeader = $context->headers['x-tcp-rtt'] ?? $context->headers['x-real-rtt'] ?? null;
221
+ $tcpRtt = $tcpRttHeader !== null ? (int)$tcpRttHeader : null;
222
+
223
+ $behaviorHeader = $context->headers['x-behavior-metrics'] ?? null;
224
+ if ($behaviorHeader) {
225
+ $metrics = json_decode($behaviorHeader, true);
226
+ if (json_last_error() === JSON_ERROR_NONE && isset($metrics['clientTimestamp'])) {
227
+ $clientTimestamp = (int)$metrics['clientTimestamp'];
228
+ $appLatency = $context->requestTimestamp - $clientTimestamp;
229
+ if ($tcpRtt !== null && $tcpRtt > 0) {
230
+ $clientToProxyDelta = $appLatency - $tcpRtt;
231
+ if ($tcpRtt < 35 && $clientToProxyDelta > 150) {
232
+ return 85;
233
+ }
234
+ } else {
235
+ if ($appLatency > 350) {
236
+ return 40;
237
+ }
238
+ }
239
+ }
240
+ }
241
+ return 0;
242
+ }
243
+ }
244
+
245
+ // --- EXEMPLE D'UTILISATION PRATIQUE ---
246
+ /*
247
+ $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
248
+ $httpVersion = $_SERVER['SERVER_PROTOCOL'] ?? '';
249
+
250
+ // Récupération des en-têtes injectés par votre Reverse-Proxy (Nginx, HAProxy, etc.)
251
+ $ja3Hash = $_SERVER['HTTP_X_JA3_HASH'] ?? null;
252
+ $ja3Raw = $_SERVER['HTTP_X_JA3_RAW'] ?? null;
253
+
254
+ // Redis facultatif pour la détection stateful de rotation UA
255
+ $redis = new \Redis();
256
+ $redis->connect('127.0.0.1', 6379);
257
+
258
+ $suspicionScore = Ja3AnomalyDetector::getJa3AnomalyScore($ja3Hash, $ja3Raw, $userAgent, $httpVersion, $redis);
228
259
  */
@@ -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