@anonympins/fingerprint 0.6.4 → 0.6.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.
@@ -53,6 +53,47 @@ class AutoTuner
53
53
  $this->onCleanup = $options['onCleanup'] ?? null;
54
54
  $this->savePath = $options['savePath'] ?? null;
55
55
  $this->validationTolerance = $options['validationTolerance'] ?? 0.15;
56
+
57
+ if (isset($this->securityConfig['logger']) && is_callable($this->securityConfig['logger']) && !isset($this->securityConfig['logger_wrapped'])) {
58
+ $originalLogger = $this->securityConfig['logger'];
59
+ $maxPercentage = $this->securityConfig['autotuning']['maxDensityPercentage'] ?? 0.02;
60
+
61
+ $this->securityConfig['logger'] = function (array $log) use ($originalLogger, $maxPercentage) {
62
+ $ip = $log['clientIp'] ?? $log['ip'] ?? null;
63
+ $subnet = $ip ? RequestUtils::getIpSubnet($ip, 24, 48) : null;
64
+ $fp = $log['deviceHash'] ?? $log['fingerprint'] ?? $log['deviceFingerprint'] ?? null;
65
+ $stableFp = $fp ? RequestUtils::extractStablePart($fp) : null;
66
+
67
+ if (count($this->trafficData) > 0) {
68
+ $total = count($this->trafficData);
69
+ $ipMatchCount = 0;
70
+ $fpMatchCount = 0;
71
+ $matchedKey = null;
72
+
73
+ foreach ($this->trafficData as $key => $existingLog) {
74
+ $logIp = $existingLog['clientIp'] ?? $existingLog['ip'] ?? null;
75
+ $logSubnet = $logIp ? RequestUtils::getIpSubnet($logIp, 24, 48) : null;
76
+ $logFp = $existingLog['deviceHash'] ?? $existingLog['fingerprint'] ?? $existingLog['deviceFingerprint'] ?? null;
77
+ $logStableFp = $logFp ? RequestUtils::extractStablePart($logFp) : null;
78
+
79
+ $isIpMatch = $subnet && $logSubnet === $subnet;
80
+ $isFpMatch = $stableFp && $logStableFp === $stableFp;
81
+ if ($isIpMatch) $ipMatchCount++;
82
+ if ($isFpMatch) $fpMatchCount++;
83
+ if ($isIpMatch || $isFpMatch) $matchedKey = $key;
84
+ }
85
+ if (($subnet && ($ipMatchCount / $total) > $maxPercentage) || ($stableFp && ($fpMatchCount / $total) > $maxPercentage)) {
86
+ if ($matchedKey !== null) {
87
+ $this->trafficData[$matchedKey]['instancesCount'] = ($this->trafficData[$matchedKey]['instancesCount'] ?? 1) + 1;
88
+ $this->trafficData[$matchedKey]['weight'] = ($this->trafficData[$matchedKey]['weight'] ?? 1.0) + 1.0;
89
+ }
90
+ return;
91
+ }
92
+ }
93
+ $originalLogger($log);
94
+ };
95
+ $this->securityConfig['logger_wrapped'] = true;
96
+ }
56
97
  }
57
98
 
58
99
  /**
@@ -32,12 +32,33 @@ class ChallengeUtils
32
32
  return abs($hash % 1000000) / 1000000;
33
33
  }
34
34
 
35
- public static function verifyGpuPow(string $seed, int $iterations, string $solution, array $sampleIndices = [0, 12, 35, 57]): bool
35
+ public static function deriveSampleIndices(string $clientIp, string $secret): array
36
+ {
37
+ $timeWindow = (int)floor(time() / (60 * 5));
38
+ $message = "{$clientIp}:{$timeWindow}";
39
+ $hmacHex = hash_hmac('sha256', $message, $secret ?: 'gpu-pow-salt');
40
+ $hmacBytes = array_values(unpack('C*', hex2bin($hmacHex)));
41
+
42
+ $indices = [];
43
+ for ($i = 0; $i < 4; $i++) {
44
+ $offset = ($i * 2) % count($hmacBytes);
45
+ $value = ($hmacBytes[$offset] << 8) | $hmacBytes[($offset + 1) % count($hmacBytes)];
46
+ $idx = $value % 64;
47
+ while (in_array($idx, $indices, true)) {
48
+ $idx = ($idx + 1) % 64;
49
+ }
50
+ $indices[] = $idx;
51
+ }
52
+ return $indices;
53
+ }
54
+
55
+ public static function verifyGpuPow(string $seed, int $iterations, string $solution, string $clientIp = '127.0.0.1', string $secret = 'gpu-pow-salt'): bool
36
56
  {
37
57
  $values = explode(',', $solution);
38
58
  if (count($values) !== 64) {
39
59
  return false;
40
60
  }
61
+ $sampleIndices = self::deriveSampleIndices($clientIp, $secret);
41
62
  $numericSeed = self::hashSeedToFloat($seed);
42
63
  $r = 3.9999;
43
64
  foreach ($sampleIndices as $idx) {
@@ -203,9 +224,22 @@ class ChallengeUtils
203
224
  return ['error' => 'Missing signature'];
204
225
  }
205
226
 
206
- // Ban the ZKP public key for 30 days
207
- $store->set("banned-zkp-y:{$zkpY}", true, 86400 * 30);
208
- return ['status' => 'synchronized'];
227
+ $peersKey = "fed-peers:{$zkpY}";
228
+ $reportedPeers = $store->get($peersKey) ?: [];
229
+ if (!is_array($reportedPeers)) {
230
+ $reportedPeers = [];
231
+ }
232
+ if (!in_array($clientIp, $reportedPeers, true)) {
233
+ $reportedPeers[] = $clientIp;
234
+ $store->set($peersKey, $reportedPeers, 86400 * 30);
235
+ }
236
+
237
+ $threshold = $config['federationConsensusThreshold'] ?? 3;
238
+ if (count($reportedPeers) >= $threshold) {
239
+ $store->set("banned-zkp-y:{$zkpY}", true, 86400 * 30);
240
+ return ['status' => 'synchronized', 'banned' => true];
241
+ }
242
+ return ['status' => 'synchronized', 'banned' => false, 'reportsCount' => count($reportedPeers)];
209
243
  }
210
244
 
211
245
  $nodeId = $params['node_id'] ?? '';
@@ -1249,10 +1249,21 @@
1249
1249
  // 1. Valider cryptographiquement la preuve avant de bannir/diffuser
1250
1250
  if (ChallengeUtils::verifyZkpProof($zkpY, $zkpT, $zkpS)) {
1251
1251
  // 2. Dédoublonner : Ne diffuser que si la clé n'est pas déjà bannie
1252
- if (!$store->has("banned-zkp-y:{$zkpY}")) {
1252
+ $peersKey = "fed-peers:{$zkpY}";
1253
+ $reportedPeers = $store->get($peersKey) ?: [];
1254
+ if (!is_array($reportedPeers)) {
1255
+ $reportedPeers = [];
1256
+ }
1257
+ if (!in_array('local', $reportedPeers, true)) {
1258
+ $reportedPeers[] = 'local';
1259
+ $store->set($peersKey, $reportedPeers, 86400 * 30);
1260
+ }
1261
+
1262
+ $threshold = $this->securityConfig['federationConsensusThreshold'] ?? 3;
1263
+ if (count($reportedPeers) >= $threshold) {
1253
1264
  $store->set("banned-zkp-y:{$zkpY}", true, 86400 * 30);
1254
- $this->broadcastBannedZkp($zkpY);
1255
1265
  }
1266
+ $this->broadcastBannedZkp($zkpY);
1256
1267
  }
1257
1268
  }
1258
1269
 
@@ -74,10 +74,43 @@ class OptimizationOperators
74
74
  $currentConfig = $context['currentConfig'] ?? null;
75
75
 
76
76
  return function (array $config) use ($trafficData, $currentConfig): array {
77
- $falsePositives = 0.0;
78
- $falseNegatives = 0.0;
79
- $totalHumans = 0.0;
80
- $totalBots = 0.0;
77
+ $threatProfiles = [
78
+ 'account_takeover' => [
79
+ 'importance' => 10.0,
80
+ 'ux_vs_security_ratio' => 0.1,
81
+ 'target_threshold' => 'block',
82
+ 'indicators' => ['requestPatternScore', 'behaviorScore', 'timeInconsistencyScore', 'clickVarianceScore']
83
+ ],
84
+ 'active_exploitation' => [
85
+ 'importance' => 8.0,
86
+ 'ux_vs_security_ratio' => 0.2,
87
+ 'target_threshold' => 'block',
88
+ 'indicators' => ['honeypotScore', 'headerAnomalyScore']
89
+ ],
90
+ 'mass_scraping' => [
91
+ 'importance' => 3.0,
92
+ 'ux_vs_security_ratio' => 0.8,
93
+ 'target_threshold' => 'low',
94
+ 'indicators' => ['requestPatternScore', 'renderingAnomalyScore', 'clientHintsInconsistencyScore']
95
+ ],
96
+ 'distributed_botnets' => [
97
+ 'importance' => 6.0,
98
+ 'ux_vs_security_ratio' => 0.5,
99
+ 'target_threshold' => 'high',
100
+ 'indicators' => ['subnetScore', 'botnetClusterScore', 'ipReputationScore', 'tlsSpoofingScore']
101
+ ],
102
+ 'basic_automation' => [
103
+ 'importance' => 5.0,
104
+ 'ux_vs_security_ratio' => 0.4,
105
+ 'target_threshold' => 'medium',
106
+ 'indicators' => ['botScore', 'tlsSpoofingScore', 'tcpAnomalyScore']
107
+ ]
108
+ ];
109
+
110
+ $threatStats = [];
111
+ foreach ($threatProfiles as $name => $profile) {
112
+ $threatStats[$name] = ['fp' => 0.0, 'fn' => 0.0, 'totalHumans' => 0.0, 'totalBots' => 0.0];
113
+ }
81
114
 
82
115
  $maxHumanScore = 0.0;
83
116
  $minBotScore = 100.0;
@@ -108,22 +141,56 @@ class OptimizationOperators
108
141
  $score = $calculateScore($log);
109
142
 
110
143
  if ($isLikelyBot) {
111
- $totalBots += $confidence;
112
144
  $minBotScore = min($minBotScore, $score);
113
- if ($score < $config['thresholds']['low']) {
114
- $falseNegatives += $confidence;
115
- }
116
145
  } elseif ($isLikelyHuman) {
117
- $totalHumans += $confidence;
118
146
  $maxHumanScore = max($maxHumanScore, $score);
119
- if ($score >= $config['thresholds']['low']) {
120
- $falsePositives += $confidence;
147
+ }
148
+
149
+ foreach ($threatProfiles as $name => $profile) {
150
+ $threatActivity = 0.0;
151
+ foreach ($profile['indicators'] as $indicator) {
152
+ $threatActivity += (float)($log['vector'][$indicator] ?? 0.0);
153
+ }
154
+ if ($threatActivity <= 0.0) {
155
+ continue;
156
+ }
157
+ $effectiveConfidence = $confidence * ($threatActivity / 100.0);
158
+ $targetThreshold = (float)($config['thresholds'][$profile['target_threshold']] ?? 20.0);
159
+
160
+ if ($isLikelyBot) {
161
+ $threatStats[$name]['totalBots'] += $effectiveConfidence;
162
+ if ($score < $targetThreshold) {
163
+ $threatStats[$name]['fn'] += $effectiveConfidence;
164
+ }
165
+ } elseif ($isLikelyHuman) {
166
+ $threatStats[$name]['totalHumans'] += $effectiveConfidence;
167
+ if ($score >= $targetThreshold) {
168
+ $threatStats[$name]['fp'] += $effectiveConfidence;
169
+ }
121
170
  }
122
171
  }
123
172
  }
124
173
 
125
- $falsePositiveRate = $totalHumans > 0.0 ? $falsePositives / $totalHumans : 0.0;
126
- $falseNegativeRate = $totalBots > 0.0 ? $falseNegatives / $totalBots : 0.0;
174
+ $weightedFpr = 0.0;
175
+ $weightedFnr = 0.0;
176
+ $totalImportance = 0.0;
177
+
178
+ foreach ($threatProfiles as $name => $profile) {
179
+ $totalImportance += $profile['importance'];
180
+ }
181
+
182
+ foreach ($threatProfiles as $name => $profile) {
183
+ $stats = $threatStats[$name];
184
+ $fpr = $stats['totalHumans'] > 0 ? $stats['fp'] / $stats['totalHumans'] : 0.0;
185
+ $fnr = $stats['totalBots'] > 0 ? $stats['fn'] / $stats['totalBots'] : 0.0;
186
+
187
+ $importanceWeight = $profile['importance'] / $totalImportance;
188
+ $uxRatio = $profile['ux_vs_security_ratio'];
189
+ $secRatio = 1.0 - $uxRatio;
190
+
191
+ $weightedFpr += $fpr * $importanceWeight * $uxRatio;
192
+ $weightedFnr += $fnr * $importanceWeight * $secRatio;
193
+ }
127
194
 
128
195
  // 3. Pénalité de dérive d'échelle (L2 Regularization par rapport au profil d'origine)
129
196
  $regularizationPenalty = 0.0;
@@ -138,8 +205,8 @@ class OptimizationOperators
138
205
  $marginOverlap = max(0.0, $maxHumanScore - $minBotScore);
139
206
  $marginPenalty = $marginOverlap / 100.0;
140
207
 
141
- $obj1 = $falsePositiveRate + ($regularizationPenalty * 0.05);
142
- $obj2 = $falseNegativeRate + $marginPenalty;
208
+ $obj1 = $weightedFpr + ($regularizationPenalty * 0.05);
209
+ $obj2 = $weightedFnr + $marginPenalty;
143
210
 
144
211
  return [$obj1, $obj2];
145
212
  };
@@ -160,9 +227,7 @@ class OptimizationOperators
160
227
  $child = $c1;
161
228
  foreach (['thresholds', 'weights', 'patterns'] as $section) {
162
229
  foreach ($child[$section] as $key => $value) {
163
- if ($key !== 'honeypotScore') {
164
- $child[$section][$key] = ($c1[$section][$key] + $c2[$section][$key]) / 2;
165
- }
230
+ $child[$section][$key] = ($c1[$section][$key] + $c2[$section][$key]) / 2;
166
231
  }
167
232
  }
168
233
  return $child;
@@ -177,7 +242,7 @@ class OptimizationOperators
177
242
  foreach (['thresholds', 'weights', 'patterns'] as $section) {
178
243
  if (isset($config[$section]) && is_array($config[$section])) {
179
244
  foreach ($config[$section] as $k => $v) {
180
- if (is_numeric($v) && $k !== 'honeypotScore') {
245
+ if (is_numeric($v)) {
181
246
  $randomVariation = 1.0 + (self::secureRandom() - 0.5) * 0.5; // Variation de +/- 25%
182
247
  $ind[$section][$k] = $v * $randomVariation;
183
248
  } else {
@@ -197,9 +262,9 @@ class OptimizationOperators
197
262
  $mutate = function (array $c, ?array $currentConfigRef = null) use ($currentConfig): array {
198
263
  $newConfig = $c;
199
264
  $sections = [
200
- ['name' => 'patterns', 'weight' => 0.5],
201
- ['name' => 'weights', 'weight' => 0.35],
202
- ['name' => 'thresholds', 'weight' => 0.15]
265
+ ['name' => 'patterns', 'weight' => 0.50],
266
+ ['name' => 'thresholds', 'weight' => 0.25],
267
+ ['name' => 'weights', 'weight' => 0.25]
203
268
  ];
204
269
  $rand = self::secureRandom();
205
270
  $cumulativeWeight = 0;
@@ -215,13 +280,16 @@ class OptimizationOperators
215
280
  $keys = array_keys($newConfig[$sectionToMutate]);
216
281
  $keyToMutate = $keys[random_int(0, count($keys) - 1)];
217
282
 
218
- if ($keyToMutate === 'honeypotScore') return $newConfig;
219
-
220
- $mutationAmount = (self::secureRandom() - 0.5) * 0.4;
221
- $newConfig[$sectionToMutate][$keyToMutate] *= (1 + $mutationAmount);
222
-
223
283
  if ($sectionToMutate === 'weights') {
224
- $newConfig[$sectionToMutate][$keyToMutate] = max(0, min(1.5, $newConfig[$sectionToMutate][$keyToMutate]));
284
+ $newConfig[$sectionToMutate][$keyToMutate] = max(0.05, min(1.5, $newConfig[$sectionToMutate][$keyToMutate] + (self::secureRandom() - 0.5) * 0.1));
285
+ } elseif ($sectionToMutate === 'thresholds') {
286
+ $newConfig[$sectionToMutate][$keyToMutate] = (int)round($newConfig[$sectionToMutate][$keyToMutate] + (self::secureRandom() - 0.5) * 5.0);
287
+ } else {
288
+ if ($keyToMutate === 'decayFactor') {
289
+ $newConfig['patterns']['decayFactor'] = max(0.8, min(0.999, $newConfig['patterns']['decayFactor'] + (self::secureRandom() - 0.5) * 0.05));
290
+ } elseif (str_contains($keyToMutate, 'Threshold') || str_contains($keyToMutate, 'Reset')) {
291
+ $newConfig['patterns'][$keyToMutate] = max(50.0, $newConfig['patterns'][$keyToMutate] + (self::secureRandom() - 0.5) * 50.0);
292
+ }
225
293
  }
226
294
 
227
295
  $refConfig = $currentConfigRef ?? $currentConfig;
@@ -53,7 +53,9 @@ class ChallengeUtilsTest extends TestCase
53
53
 
54
54
  // Tamper with value on verified sample index 12
55
55
  $tampered = $solutions;
56
- $tampered[12] = (string)((float)$tampered[12] + 0.0002); // Out of tolerance
56
+ $sampleIndices = ChallengeUtils::deriveSampleIndices('127.0.0.1', 'gpu-pow-salt');
57
+ $tamperedIndex = $sampleIndices[0];
58
+ $tampered[$tamperedIndex] = (string)((float)$tampered[$tamperedIndex] + 0.0002); // Out of tolerance
57
59
  $this->assertFalse(ChallengeUtils::verifyGpuPow($seed, $iterations, implode(',', $tampered)));
58
60
  }
59
61
 
@@ -85,8 +87,10 @@ class ChallengeUtilsTest extends TestCase
85
87
  }
86
88
  $solutionString = implode(',', $solutions);
87
89
  $this->assertTrue(ChallengeUtils::verifyGpuPow($seed, $iterations, $solutionString));
90
+ $sampleIndices = ChallengeUtils::deriveSampleIndices('127.0.0.1', 'gpu-pow-salt');
91
+ $tamperedIndex = $sampleIndices[0];
88
92
  $tamperedSolutions = $solutions;
89
- $tamperedSolutions[0] = (string)((float)$tamperedSolutions[0] + 0.1);
93
+ $tamperedSolutions[$tamperedIndex] = (string)((float)$tamperedSolutions[$tamperedIndex] + 0.1);
90
94
  $this->assertFalse(ChallengeUtils::verifyGpuPow($seed, $iterations, implode(',', $tamperedSolutions)));
91
95
  }
92
96