@anonympins/fingerprint 0.7.1 → 0.7.3
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.
- package/CHANGELOG.md +28 -3
- package/README.md +1 -1
- package/package.json +1 -1
- package/public/fingerprint-anti-bot.zip +0 -0
- package/src/js/fingerprint.js +106 -29
- package/src/js/fingerprint.utils.js +32 -5
- package/src/js/tests/fingerprint.test.js +91 -4
- package/src/js/tests/threatIntel.test.js +76 -0
- package/src/php/AutoTuner.php +36 -18
- package/src/php/Challenge/ChallengeUtils.php +107 -225
- package/src/php/DirectFingerprint.php +16 -4
- package/src/php/FingerprintClient.php +17 -19
- package/src/php/FingerprintEngine.php +114 -51
- package/src/php/Ja3AnomalyDetector.php +20 -12
- package/src/php/Optimization/Optimization.php +5 -1
- package/src/php/Optimization/OptimizationOperators.php +5 -1
- package/src/php/Optimization/ProblemInitializers.php +16 -4
- package/src/php/ProblemManager.php +20 -14
- package/src/php/Tests/FingerprintEngineTest.php +705 -662
- package/src/php/Tests/ThreatIntelTest.php +64 -0
- package/src/php/Utils/BlockList.php +99 -99
- package/src/php/Utils/Env.php +51 -49
- package/src/php/Utils/MaliciousPatterns.php +74 -74
- package/src/php/Utils/RequestUtils.php +22 -5
- package/src/php/WordPress/WpDbStore.php +4 -0
- package/src/php/WordPress/fingerprint-anti-bot.php +559 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +262 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +268 -0
- package/src/php/WordPress/package.php +102 -4
- package/src/php/WordPress/readme.txt +36 -0
- package/public/fingerprint-wordpress.zip +0 -0
- package/src/js/fingerprint.client.obfuscated.js +0 -1
- package/src/php/WordPress/fingerprint-wordpress.php +0 -217
package/src/php/AutoTuner.php
CHANGED
|
@@ -129,7 +129,8 @@ class AutoTuner
|
|
|
129
129
|
try {
|
|
130
130
|
call_user_func($this->onCleanup, $removed);
|
|
131
131
|
} catch (\Throwable $e) {
|
|
132
|
-
|
|
132
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
|
|
133
|
+
$this->logError("[AutoTuning] Error in onCleanup callback: " . $e->getMessage());
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
}
|
|
@@ -155,24 +156,24 @@ class AutoTuner
|
|
|
155
156
|
|
|
156
157
|
if (count($sanitizedData) < $this->minDataPoints || !$hasEnoughSignal) {
|
|
157
158
|
if (count($sanitizedData) < $this->minDataPoints) {
|
|
158
|
-
|
|
159
|
+
$this->logMessage(sprintf("[AutoTuning] Postponed: %d/%d data points collected.\n", count($sanitizedData), $this->minDataPoints));
|
|
159
160
|
} else {
|
|
160
|
-
|
|
161
|
+
$this->logMessage(sprintf("[AutoTuning] Postponed: Insufficient confidence signals (Ratio: %.2f%% < %.2f%% and count: %d < %d).\n", $highConfidenceRatio * 100, $minConfidenceRatio * 100, $highConfidenceLogs, $minHighConfidenceCount));
|
|
161
162
|
}
|
|
162
163
|
return;
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
if (count($this->trafficData) > $this->maxDataPoints) {
|
|
166
|
-
|
|
167
|
+
$this->logMessage(sprintf("[AutoTuning] Traffic log reached %d entries (max: %d). Truncating oldest logs.\n", count($this->trafficData), $this->maxDataPoints));
|
|
167
168
|
$this->trafficData = array_slice($this->trafficData, count($this->trafficData) - $this->maxDataPoints);
|
|
168
169
|
}
|
|
169
170
|
|
|
170
|
-
|
|
171
|
+
$this->logMessage(sprintf("[AutoTuning] Starting complete optimization cycle with %d sanitized data points.\n", count($sanitizedData)));
|
|
171
172
|
|
|
172
173
|
$paretoFront = OptimizationOperators::solveFullSecurityTuning(['trafficData' => $sanitizedData, 'currentConfig' => $this->securityConfig], []);
|
|
173
174
|
|
|
174
175
|
if (empty($paretoFront)) {
|
|
175
|
-
|
|
176
|
+
$this->logMessage("[AutoTuning] Optimization returned no solutions.\n");
|
|
176
177
|
return;
|
|
177
178
|
}
|
|
178
179
|
|
|
@@ -192,7 +193,7 @@ class AutoTuner
|
|
|
192
193
|
|
|
193
194
|
$filteredFront = array_filter($paretoFront, fn ($p) => $isValidSecurityConfig($p['solution']));
|
|
194
195
|
if (empty($filteredFront)) {
|
|
195
|
-
|
|
196
|
+
$this->logMessage("[AutoTuning] Warning: All Pareto solutions violated sanity guardrails. Restoring raw front.\n");
|
|
196
197
|
$filteredFront = $paretoFront;
|
|
197
198
|
} else {
|
|
198
199
|
$filteredFront = array_values($filteredFront);
|
|
@@ -277,7 +278,8 @@ class AutoTuner
|
|
|
277
278
|
$validationTolerance = $this->securityConfig['autotuning']['validationTolerance'] ?? $this->validationTolerance;
|
|
278
279
|
|
|
279
280
|
if ($proposedFPR > $currentFPR + $validationTolerance || $proposedFNR > $currentFNR + $validationTolerance) {
|
|
280
|
-
|
|
281
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system alert
|
|
282
|
+
$this->logError(sprintf(
|
|
281
283
|
"[AutoTuning] [SECURITY ALERT] Proposed configuration rejected due to instability/poisoning risk! Proposed FPR: %.4f (Current: %.4f), Proposed FNR: %.4f (Current: %.4f)",
|
|
282
284
|
$proposedFPR, $currentFPR, $proposedFNR, $currentFNR
|
|
283
285
|
));
|
|
@@ -300,22 +302,23 @@ class AutoTuner
|
|
|
300
302
|
|
|
301
303
|
self::$lastBestSolution = $bestSolution;
|
|
302
304
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
+
$this->logMessage("[AutoTuning] New optimized security configuration applied.\n");
|
|
306
|
+
$this->logMessage("[AutoTuning] Objectives achieved: " . json_encode([
|
|
305
307
|
'falsePositiveRate' => round($bestSolution['objectives'][0], 4),
|
|
306
308
|
'falseNegativeRate' => round($bestSolution['objectives'][1], 4)
|
|
307
|
-
]) . "\n";
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
]) . "\n");
|
|
310
|
+
$this->logMessage("[AutoTuning] New thresholds: " . json_encode($this->securityConfig['thresholds']) . "\n");
|
|
311
|
+
$this->logMessage("[AutoTuning] New weights: " . json_encode($this->securityConfig['weights']) . "\n");
|
|
312
|
+
$this->logMessage("[AutoTuning] New patterns: " . json_encode($this->securityConfig['patterns']) . "\n");
|
|
311
313
|
|
|
312
314
|
// Persist best configuration if savePath is configured
|
|
313
315
|
if ($this->savePath !== null) {
|
|
314
316
|
try {
|
|
315
317
|
file_put_contents($this->savePath, json_encode($bestSolution['solution'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
|
316
|
-
|
|
318
|
+
$this->logMessage("[AutoTuning] Best configuration saved to: {$this->savePath}\n");
|
|
317
319
|
} catch (\Throwable $e) {
|
|
318
|
-
|
|
320
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
|
|
321
|
+
$this->logError("[AutoTuning] Error saving optimized configuration: " . $e->getMessage());
|
|
319
322
|
}
|
|
320
323
|
}
|
|
321
324
|
|
|
@@ -325,13 +328,28 @@ class AutoTuner
|
|
|
325
328
|
try {
|
|
326
329
|
call_user_func($this->onCleanup, $cleared);
|
|
327
330
|
} catch (\Throwable $e) {
|
|
328
|
-
|
|
331
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
|
|
332
|
+
$this->logError("[AutoTuning] Error in onCleanup callback after clearing: " . $e->getMessage());
|
|
329
333
|
}
|
|
330
334
|
}
|
|
331
|
-
|
|
335
|
+
$this->logMessage(sprintf("[AutoTuning] Explicitly cleared %d processed traffic data points.\n", count($cleared)));
|
|
332
336
|
}
|
|
333
337
|
}
|
|
334
338
|
|
|
339
|
+
private function logMessage(string $message): void
|
|
340
|
+
{
|
|
341
|
+
if (PHP_SAPI === 'cli') {
|
|
342
|
+
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- CLI message output
|
|
343
|
+
echo $message;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private function logError(string $message): void
|
|
348
|
+
{
|
|
349
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- AutoTuning system log
|
|
350
|
+
error_log($message);
|
|
351
|
+
}
|
|
352
|
+
|
|
335
353
|
/**
|
|
336
354
|
* Returns the latest best solution found by the auto-tuner.
|
|
337
355
|
* @return array<string, mixed>|null
|
|
@@ -178,7 +178,8 @@ class ChallengeUtils
|
|
|
178
178
|
$peers = $config['federatedPeers'] ?? [];
|
|
179
179
|
if (!empty($peers)) {
|
|
180
180
|
$allowedHosts = array_map(function ($url) {
|
|
181
|
-
|
|
181
|
+
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
|
|
182
|
+
$host = function_exists('wp_parse_url') ? wp_parse_url($url, PHP_URL_HOST) : parse_url($url, PHP_URL_HOST);
|
|
182
183
|
return !empty($host) ? $host : $url;
|
|
183
184
|
}, $peers);
|
|
184
185
|
|
|
@@ -188,9 +189,9 @@ class ChallengeUtils
|
|
|
188
189
|
}
|
|
189
190
|
$zkpY = $params['zkpY'] ?? '';
|
|
190
191
|
$headers = function_exists('getallheaders') ? array_change_key_case(getallheaders(), CASE_LOWER) : [];
|
|
191
|
-
$sigEd25519 = $params['signature_ed25519'] ?? $headers['x-federation-signature-ed25519'] ??
|
|
192
|
-
$sigHmac = $params['signature'] ?? $headers['x-federation-signature'] ??
|
|
193
|
-
$timestamp = (int)($params['timestamp'] ?? $headers['x-federation-timestamp'] ??
|
|
192
|
+
$sigEd25519 = $params['signature_ed25519'] ?? $headers['x-federation-signature-ed25519'] ?? self::getSanitizedServerVar('HTTP_X_FEDERATION_SIGNATURE_ED25519', '');
|
|
193
|
+
$sigHmac = $params['signature'] ?? $headers['x-federation-signature'] ?? self::getSanitizedServerVar('HTTP_X_FEDERATION_SIGNATURE', '');
|
|
194
|
+
$timestamp = (int)($params['timestamp'] ?? $headers['x-federation-timestamp'] ?? self::getSanitizedServerVar('HTTP_X_FEDERATION_TIMESTAMP', 0));
|
|
194
195
|
|
|
195
196
|
if (empty($zkpY) || empty($timestamp)) {
|
|
196
197
|
return ['error' => 'Missing threat intel parameters'];
|
|
@@ -205,7 +206,7 @@ class ChallengeUtils
|
|
|
205
206
|
$msg = "{$timestamp}:{$zkpY}";
|
|
206
207
|
|
|
207
208
|
if (!empty($sigEd25519)) {
|
|
208
|
-
$publicKey = $config['ed25519_public_key'] ??
|
|
209
|
+
$publicKey = $config['ed25519_public_key'] ?? Env::get('ED25519_PUBLIC_KEY');
|
|
209
210
|
if (!$publicKey) {
|
|
210
211
|
return ['error' => 'Missing public key for asymmetric verification'];
|
|
211
212
|
}
|
|
@@ -298,8 +299,8 @@ class ChallengeUtils
|
|
|
298
299
|
|
|
299
300
|
switch ($op) {
|
|
300
301
|
case 'register':
|
|
301
|
-
$clientIp =
|
|
302
|
-
$seed = $params['seed'] ?? '';
|
|
302
|
+
$clientIp = self::getSanitizedServerVar('REMOTE_ADDR', '127.0.0.1');
|
|
303
|
+
$seed = $params['seed'] ?? ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
|
303
304
|
self::registerCooperativeNode($clientIp, $nodeId, $seed);
|
|
304
305
|
return ['status' => 'registered'];
|
|
305
306
|
|
|
@@ -504,7 +505,7 @@ class ChallengeUtils
|
|
|
504
505
|
return 'ed25519.' . $base64UrlEncode($serialized) . '.' . $base64UrlEncode($signature);
|
|
505
506
|
}
|
|
506
507
|
} catch (\Throwable $e) {
|
|
507
|
-
|
|
508
|
+
self::logError("[ChallengeUtils] Ed25519 signing failed: " . $e->getMessage());
|
|
508
509
|
}
|
|
509
510
|
}
|
|
510
511
|
|
|
@@ -540,7 +541,7 @@ class ChallengeUtils
|
|
|
540
541
|
|
|
541
542
|
$ed25519PubKey = Env::get('ED25519_PUBLIC_KEY');
|
|
542
543
|
if (!$ed25519PubKey) {
|
|
543
|
-
|
|
544
|
+
self::logError("[ChallengeUtils] ED25519_PUBLIC_KEY is not defined in environment.");
|
|
544
545
|
return null;
|
|
545
546
|
}
|
|
546
547
|
|
|
@@ -551,7 +552,7 @@ class ChallengeUtils
|
|
|
551
552
|
return null;
|
|
552
553
|
}
|
|
553
554
|
} catch (\Throwable $e) {
|
|
554
|
-
|
|
555
|
+
self::logError("[ChallengeUtils] Ed25519 verification failed: " . $e->getMessage());
|
|
555
556
|
return null;
|
|
556
557
|
}
|
|
557
558
|
|
|
@@ -737,7 +738,7 @@ class ChallengeUtils
|
|
|
737
738
|
): ?string {
|
|
738
739
|
// En mode HTTP (non sécurisé), aucun calcul SHA-256 n'est exigé
|
|
739
740
|
if (!empty($challengeContext['isHttp'])) {
|
|
740
|
-
|
|
741
|
+
self::logError('[FP Server Verify] Mode HTTP détecté (insecure policy) : validation sans SHA-256 acceptée.');
|
|
741
742
|
$expiry = (int)floor(microtime(true) * 1000) + $ticketTtl;
|
|
742
743
|
return self::generateStatelessTicket([
|
|
743
744
|
'expiry' => $expiry,
|
|
@@ -751,7 +752,7 @@ class ChallengeUtils
|
|
|
751
752
|
$baseBlock = $challengeContext['baseBlock'] ?? null;
|
|
752
753
|
|
|
753
754
|
if ($cpuTargetHex === null || $baseBlock === null) {
|
|
754
|
-
|
|
755
|
+
self::logError('[FP Server Verify] Invalid challenge context. Missing cpuTarget or baseBlock.');
|
|
755
756
|
return null;
|
|
756
757
|
}
|
|
757
758
|
|
|
@@ -763,7 +764,7 @@ class ChallengeUtils
|
|
|
763
764
|
$isValid = strcmp($hash, $paddedTarget) < 0;
|
|
764
765
|
|
|
765
766
|
if ($isValid) {
|
|
766
|
-
|
|
767
|
+
self::logError('[FP Server Verify] CPU PoW verification PASSED.');
|
|
767
768
|
|
|
768
769
|
$expiry = (int)floor(microtime(true) * 1000) + $ticketTtl;
|
|
769
770
|
$payload = [
|
|
@@ -776,7 +777,7 @@ class ChallengeUtils
|
|
|
776
777
|
}
|
|
777
778
|
|
|
778
779
|
// Log details on failure
|
|
779
|
-
|
|
780
|
+
self::logError(sprintf(
|
|
780
781
|
'[FP Server Verify] CPU PoW verification FAILED. Details: hashCalculated=0x%s, target=0x%s',
|
|
781
782
|
$hash,
|
|
782
783
|
$cpuTargetHex
|
|
@@ -888,11 +889,11 @@ class ChallengeUtils
|
|
|
888
889
|
}
|
|
889
890
|
$maxAllowedMemDifficulty = 128; // 128MB
|
|
890
891
|
if ($difficulty > $maxAllowedMemDifficulty) {
|
|
891
|
-
|
|
892
|
+
self::logError("[Security] Memory PoW verification attempt with excessive difficulty: {$difficulty}MB. Denied.");
|
|
892
893
|
return false;
|
|
893
894
|
}
|
|
894
895
|
|
|
895
|
-
if (
|
|
896
|
+
if ($solution === '') {
|
|
896
897
|
return false;
|
|
897
898
|
}
|
|
898
899
|
|
|
@@ -1020,7 +1021,7 @@ class ChallengeUtils
|
|
|
1020
1021
|
dirname(__DIR__, 1) . '/assets/pow.solver.inline.js',
|
|
1021
1022
|
];
|
|
1022
1023
|
if (defined('ABSPATH')) {
|
|
1023
|
-
$wpPluginSolver = ABSPATH . 'wp-content/plugins/fingerprint-
|
|
1024
|
+
$wpPluginSolver = ABSPATH . 'wp-content/plugins/fingerprint-anti-bot/assets/pow.solver.inline.js';
|
|
1024
1025
|
if (file_exists($wpPluginSolver)) {
|
|
1025
1026
|
return file_get_contents($wpPluginSolver) ?: '';
|
|
1026
1027
|
}
|
|
@@ -1030,7 +1031,7 @@ class ChallengeUtils
|
|
|
1030
1031
|
return file_get_contents($solverPath) ?: '';
|
|
1031
1032
|
}
|
|
1032
1033
|
}
|
|
1033
|
-
|
|
1034
|
+
self::logError("[ChallengeUtils] Erreur: Le fichier pow.solver.inline.js n'a pas été trouvé à l'emplacement attendu.");
|
|
1034
1035
|
return '';
|
|
1035
1036
|
}
|
|
1036
1037
|
|
|
@@ -1052,173 +1053,28 @@ class ChallengeUtils
|
|
|
1052
1053
|
$safeNonce = json_encode($nonce, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
1053
1054
|
$safeClientSecret = json_encode($clientSecret, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
1054
1055
|
|
|
1055
|
-
$challengeScript =
|
|
1056
|
-
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
const
|
|
1060
|
-
const
|
|
1061
|
-
const
|
|
1062
|
-
const
|
|
1063
|
-
const
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
await fetch(window.location.pathname + "?coop_op=webrtc_signal&node_id=" + nodeId + "&target_peer_id=" + targetId + "&signal_type=" + type + "&signal_data=" + encodeURIComponent(data) + "&coop_sig=" + sig);
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
document.getElementById('loader').innerText = '⚙️ Checking persistent local storage...';
|
|
1081
|
-
await new Promise(r => setTimeout(r, 10));
|
|
1082
|
-
|
|
1083
|
-
try {
|
|
1084
|
-
await window.initializeSpace(nonce + ":" + clientSecret, sizeMb);
|
|
1085
|
-
|
|
1086
|
-
if (peerId && peerBlockIdx !== -1) {
|
|
1087
|
-
// Enregistrement coopératif
|
|
1088
|
-
const sig = await signCoop("register", nodeId, nonce + ":" + clientSecret);
|
|
1089
|
-
await fetch(window.location.pathname + "?coop_op=register&node_id=" + nodeId + "&seed=" + encodeURIComponent(nonce + ":" + clientSecret) + "&coop_sig=" + sig);
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
const peerConnections = {};
|
|
1093
|
-
|
|
1094
|
-
// Écoute des signaux WebRTC et requêtes entrantes
|
|
1095
|
-
setInterval(async () => {
|
|
1096
|
-
try {
|
|
1097
|
-
const sigWebrtc = await signCoop("poll_signals", nodeId);
|
|
1098
|
-
const resWebrtc = await fetch(window.location.pathname + "?coop_op=poll_signals&node_id=" + nodeId + "&coop_sig=" + sigWebrtc);
|
|
1099
|
-
const dataWebrtc = await resWebrtc.json();
|
|
1100
|
-
if (dataWebrtc.signals && dataWebrtc.signals.length > 0) {
|
|
1101
|
-
for (const sig of dataWebrtc.signals) {
|
|
1102
|
-
const fromId = sig.from_peer_id;
|
|
1103
|
-
if (sig.signal_type === 'offer') {
|
|
1104
|
-
const pc = new RTCPeerConnection({ iceServers: [] });
|
|
1105
|
-
peerConnections[fromId] = pc;
|
|
1106
|
-
pc.onicecandidate = (e) => {
|
|
1107
|
-
if (e.candidate) sendWebRtcSignal(fromId, 'candidate', JSON.stringify(e.candidate));
|
|
1108
|
-
};
|
|
1109
|
-
pc.ondatachannel = (e) => {
|
|
1110
|
-
const dc = e.channel;
|
|
1111
|
-
dc.onmessage = async (evt) => {
|
|
1112
|
-
try {
|
|
1113
|
-
const req = JSON.parse(evt.data);
|
|
1114
|
-
if (req.type === 'get_block') {
|
|
1115
|
-
document.getElementById('loader').innerText = '📤 Transfert direct P2P (WebRTC) du bloc vers le pair...';
|
|
1116
|
-
const blockData = await window.readSpaceBlock(req.block_idx);
|
|
1117
|
-
dc.send(JSON.stringify({ type: 'block_data', block_data: blockData }));
|
|
1118
|
-
}
|
|
1119
|
-
} catch (err) {}
|
|
1120
|
-
};
|
|
1121
|
-
};
|
|
1122
|
-
await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(sig.signal_data)));
|
|
1123
|
-
const answer = await pc.createAnswer();
|
|
1124
|
-
await pc.setLocalDescription(answer);
|
|
1125
|
-
await sendWebRtcSignal(fromId, 'answer', JSON.stringify(answer));
|
|
1126
|
-
} else if (sig.signal_type === 'answer' && peerConnections[fromId]) {
|
|
1127
|
-
await peerConnections[fromId].setRemoteDescription(new RTCSessionDescription(JSON.parse(sig.signal_data)));
|
|
1128
|
-
} else if (sig.signal_type === 'candidate' && peerConnections[fromId]) {
|
|
1129
|
-
await peerConnections[fromId].addIceCandidate(new RTCIceCandidate(JSON.parse(sig.signal_data)));
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
} catch (e) {}
|
|
1134
|
-
|
|
1135
|
-
try {
|
|
1136
|
-
const sig = await signCoop("poll_requests", nodeId);
|
|
1137
|
-
const res = await fetch(window.location.pathname + "?coop_op=poll_requests&node_id=" + nodeId + "&coop_sig=" + sig);
|
|
1138
|
-
const data = await res.json();
|
|
1139
|
-
if (data.requests && data.requests.length > 0) {
|
|
1140
|
-
for (const req of data.requests) {
|
|
1141
|
-
document.getElementById('loader').innerText = '📤 Transfert coopératif de bloc vers le pair...';
|
|
1142
|
-
const blockData = await window.readSpaceBlock(req.block_idx);
|
|
1143
|
-
const respSig = await signCoop("respond_block", nodeId, req.requester_id + ":" + req.req_id + ":" + blockData);
|
|
1144
|
-
await fetch(window.location.pathname + "?coop_op=respond_block&node_id=" + nodeId + "&requester_id=" + req.requester_id + "&req_id=" + req.req_id + "&block_data=" + encodeURIComponent(blockData) + "&coop_sig=" + respSig);
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
} catch (e) {
|
|
1148
|
-
console.error("Cooperative polling error", e);
|
|
1149
|
-
}
|
|
1150
|
-
}, 800);
|
|
1151
|
-
|
|
1152
|
-
let peerBlock = "";
|
|
1153
|
-
if (peerId && peerBlockIdx !== -1) {
|
|
1154
|
-
document.getElementById('loader').innerText = '📥 Connexion WebRTC P2P directe au pair (' + peerId + ')...';
|
|
1155
|
-
|
|
1156
|
-
// 1. Échange direct P2P via WebRTC DataChannel (charge serveur = 0)
|
|
1157
|
-
const webrtcTransferPromise = new Promise(async (resolve) => {
|
|
1158
|
-
if (!window.RTCPeerConnection) return resolve(null);
|
|
1159
|
-
try {
|
|
1160
|
-
const pc = new RTCPeerConnection({ iceServers: [] });
|
|
1161
|
-
peerConnections[peerId] = pc;
|
|
1162
|
-
const dc = pc.createDataChannel("pospace-transfer");
|
|
1163
|
-
pc.onicecandidate = (e) => {
|
|
1164
|
-
if (e.candidate) sendWebRtcSignal(peerId, 'candidate', JSON.stringify(e.candidate));
|
|
1165
|
-
};
|
|
1166
|
-
dc.onopen = () => {
|
|
1167
|
-
dc.send(JSON.stringify({ type: 'get_block', block_idx: peerBlockIdx }));
|
|
1168
|
-
};
|
|
1169
|
-
dc.onmessage = (e) => {
|
|
1170
|
-
try {
|
|
1171
|
-
const msg = JSON.parse(e.data);
|
|
1172
|
-
if (msg.type === 'block_data' && msg.block_data) {
|
|
1173
|
-
resolve(msg.block_data);
|
|
1174
|
-
}
|
|
1175
|
-
} catch (err) {}
|
|
1176
|
-
};
|
|
1177
|
-
const offer = await pc.createOffer();
|
|
1178
|
-
await pc.setLocalDescription(offer);
|
|
1179
|
-
await sendWebRtcSignal(peerId, 'offer', JSON.stringify(offer));
|
|
1180
|
-
} catch (err) {
|
|
1181
|
-
resolve(null);
|
|
1182
|
-
}
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
|
-
const webrtcTimeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), 5000));
|
|
1186
|
-
peerBlock = await Promise.race([webrtcTransferPromise, webrtcTimeoutPromise]);
|
|
1187
|
-
|
|
1188
|
-
if (peerBlock) {
|
|
1189
|
-
document.getElementById('loader').innerText = '⚡ Bloc reçu en direct via WebRTC P2P sans transit serveur !';
|
|
1190
|
-
} else {
|
|
1191
|
-
// 2. Repli vers le relais HTTP si WebRTC échoue
|
|
1192
|
-
document.getElementById('loader').innerText = '⚠️ WebRTC indisponible. Téléchargement via relais HTTP...';
|
|
1193
|
-
const reqId = Math.random().toString(36).substring(2);
|
|
1194
|
-
const reqSig = await signCoop("request_peer_block", nodeId, peerId + ":" + peerBlockIdx + ":" + reqId);
|
|
1195
|
-
await fetch(window.location.pathname + "?coop_op=request_peer_block&node_id=" + nodeId + "&peer_id=" + peerId + "&block_idx=" + peerBlockIdx + "&req_id=" + reqId + "&coop_sig=" + reqSig);
|
|
1196
|
-
|
|
1197
|
-
let attempts = 0;
|
|
1198
|
-
while (attempts < coopTimeout) {
|
|
1199
|
-
const pollSig = await signCoop("poll_response", nodeId, reqId);
|
|
1200
|
-
const res = await fetch(window.location.pathname + "?coop_op=poll_response&node_id=" + nodeId + "&req_id=" + reqId + "&coop_sig=" + pollSig);
|
|
1201
|
-
const data = await res.json();
|
|
1202
|
-
if (data.status === 'ready') {
|
|
1203
|
-
peerBlock = data.block_data;
|
|
1204
|
-
break;
|
|
1205
|
-
}
|
|
1206
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
1207
|
-
attempts++;
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
document.getElementById('loader').innerText = '⚙️ Génération de la Preuve d\\'Espace...';
|
|
1213
|
-
const hash = await window.solveSpaceChallenge(nonce + ":" + clientSecret, queries, nonce, clientSecret, peerBlock);
|
|
1214
|
-
|
|
1215
|
-
window.location.href = path + "?pow_type=pospace&pow_nonce=" + nonce + "&pow_solution_space=" + hash + (peerBlock ? "&pow_coop=1" : "");
|
|
1216
|
-
} catch(e) {
|
|
1217
|
-
document.getElementById('loader').innerText = "Error initializing local storage: " + e.message;
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
solve();
|
|
1221
|
-
JS;
|
|
1056
|
+
$challengeScript = 'async function solve() {' . "\n"
|
|
1057
|
+
. ' const nonce = ' . $safeNonce . ";\n"
|
|
1058
|
+
. ' const path = ' . $safePath . ";\n"
|
|
1059
|
+
. ' const clientSecret = ' . $safeClientSecret . ";\n"
|
|
1060
|
+
. ' const queries = ' . $queriesJson . ";\n"
|
|
1061
|
+
. ' const sizeMb = ' . $sizeMb . ";\n"
|
|
1062
|
+
. ' const nodeId = "' . $nodeId . "\";\n"
|
|
1063
|
+
. ' const peerId = "' . $peerId . "\";\n"
|
|
1064
|
+
. ' const peerBlockIdx = ' . $peerBlockIdx . ";\n"
|
|
1065
|
+
. ' const coopTimeout = ' . $coopTimeout . ";\n"
|
|
1066
|
+
. ' document.getElementById("loader").innerText = "⚙️ Checking persistent local storage...";' . "\n"
|
|
1067
|
+
. ' await new Promise(r => setTimeout(r, 10));' . "\n"
|
|
1068
|
+
. ' try {' . "\n"
|
|
1069
|
+
. ' await window.initializeSpace(nonce + ":" + clientSecret, sizeMb);' . "\n"
|
|
1070
|
+
. ' document.getElementById("loader").innerText = "⚙️ Proof of Space generation...";' . "\n"
|
|
1071
|
+
. ' const hash = await window.solveSpaceChallenge(nonce + ":" + clientSecret, queries, nonce, clientSecret, "");' . "\n"
|
|
1072
|
+
. ' window.location.href = path + "?pow_type=pospace&pow_nonce=" + nonce + "&pow_solution_space=" + hash;' . "\n"
|
|
1073
|
+
. ' } catch(e) {' . "\n"
|
|
1074
|
+
. ' document.getElementById("loader").innerText = "Error initializing local storage: " + e.message;' . "\n"
|
|
1075
|
+
. ' }' . "\n"
|
|
1076
|
+
. '}' . "\n"
|
|
1077
|
+
. 'solve();';
|
|
1222
1078
|
|
|
1223
1079
|
return "<html><head><title>Security Check</title></head><body style=\"font-family:sans-serif; text-align:center; padding-top:50px;\"><h1>Security Check (Level 2)</h1><p>We are verifying your storage allocation. This may take a few seconds on first load.</p><div id=\"loader\" style=\"margin:20px;\">⚙️ Initializing storage space...</div><script>{$solverCode}</script><script>{$challengeScript}</script></body></html>";
|
|
1224
1080
|
}
|
|
@@ -1267,7 +1123,7 @@ JS;
|
|
|
1267
1123
|
|
|
1268
1124
|
$trapLinks = [];
|
|
1269
1125
|
foreach ($trapUrls as $index => $url) {
|
|
1270
|
-
$nestingType =
|
|
1126
|
+
$nestingType = function_exists('wp_rand') ? wp_rand(0, 2) : random_int(0, 2);
|
|
1271
1127
|
$innerHtml = ($nestingType === 1) ? "<b>> " . ($index + 1) . "</b>" : (($nestingType === 2) ? "<i>> " . ($index + 1) . "</i>" : "<span>> " . ($index + 1) . "</span>");
|
|
1272
1128
|
$trapLinks[] = "<a href=\"{$url}\" tabindex=\"-1\">{$innerHtml}</a>";
|
|
1273
1129
|
}
|
|
@@ -1279,45 +1135,38 @@ JS;
|
|
|
1279
1135
|
$safeClientSecret = json_encode($clientSecret, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);
|
|
1280
1136
|
$isHttpJs = $isHttp ? 'true' : 'false';
|
|
1281
1137
|
|
|
1282
|
-
$challengeScript =
|
|
1283
|
-
|
|
1284
|
-
const
|
|
1285
|
-
const
|
|
1286
|
-
const
|
|
1287
|
-
const
|
|
1288
|
-
const
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
const finalUrl = path + "?pow_type=cpu_mem&pow_nonce=" + nonce + "&pow_solution_cpu=" + cpuSolution + "&pow_solution_mem=" + encodeURIComponent(JSON.stringify(memSolution));
|
|
1317
|
-
window.location.href = finalUrl;
|
|
1318
|
-
}
|
|
1319
|
-
solve();
|
|
1320
|
-
JS;
|
|
1138
|
+
$challengeScript = 'async function solve() {' . "\n"
|
|
1139
|
+
. ' const nonce = ' . $safeNonce . ";\n"
|
|
1140
|
+
. ' const path = ' . $safePath . ";\n"
|
|
1141
|
+
. ' const clientSecret = ' . $safeClientSecret . ";\n"
|
|
1142
|
+
. ' const isHttp = ' . $isHttpJs . ";\n"
|
|
1143
|
+
. ' const cpuTarget = BigInt("0x" + "' . $target . "\");\n"
|
|
1144
|
+
. ' const memDifficulty = ' . $memoryDifficulty . ";\n"
|
|
1145
|
+
. ' const baseBlock = new Uint8Array(' . $baseBlockBytes . ");\n"
|
|
1146
|
+
. ' if (isHttp) {' . "\n"
|
|
1147
|
+
. ' document.getElementById("loader").innerText = "HTTP connection: quick acknowledgment...";' . "\n"
|
|
1148
|
+
. ' await new Promise(r => setTimeout(r, 150));' . "\n"
|
|
1149
|
+
. ' window.location.href = path + "?pow_type=cpu_mem&pow_nonce=" + nonce + "&pow_solution_cpu=http_simulacre_ack&pow_solution_mem=0";' . "\n"
|
|
1150
|
+
. ' return;' . "\n"
|
|
1151
|
+
. ' }' . "\n"
|
|
1152
|
+
. ' document.getElementById("loader").innerText = "⚙️ Performing CPU security calculation...";' . "\n"
|
|
1153
|
+
. ' const cpuSolution = await window.solveCpuChallengeInline(baseBlock, cpuTarget, (progress) => {});' . "\n"
|
|
1154
|
+
. ' if (memDifficulty > 0) {' . "\n"
|
|
1155
|
+
. ' document.getElementById("loader").innerText = "⚙️ Performing memory allocation and calculation... (" + memDifficulty + " MB)";' . "\n"
|
|
1156
|
+
. ' await new Promise(r => setTimeout(r, 10));' . "\n"
|
|
1157
|
+
. ' }' . "\n"
|
|
1158
|
+
. ' let memSolution = 0;' . "\n"
|
|
1159
|
+
. ' try {' . "\n"
|
|
1160
|
+
. ' const memSeed = ":" + nonce + ":" + clientSecret;' . "\n"
|
|
1161
|
+
. ' memSolution = await window.solveMemoryChallenge(memSeed, memDifficulty);' . "\n"
|
|
1162
|
+
. ' } catch(e) {' . "\n"
|
|
1163
|
+
. ' document.getElementById("loader").innerText = "Error: Insufficient memory. Please refresh.";' . "\n"
|
|
1164
|
+
. ' return;' . "\n"
|
|
1165
|
+
. ' }' . "\n"
|
|
1166
|
+
. ' const finalUrl = path + "?pow_type=cpu_mem&pow_nonce=" + nonce + "&pow_solution_cpu=" + cpuSolution + "&pow_solution_mem=" + encodeURIComponent(JSON.stringify(memSolution));' . "\n"
|
|
1167
|
+
. ' window.location.href = finalUrl;' . "\n"
|
|
1168
|
+
. '}' . "\n"
|
|
1169
|
+
. 'solve();';
|
|
1321
1170
|
|
|
1322
1171
|
$htmlTemplate = '<html><head><title>Advanced Security Check</title></head><body style="font-family:sans-serif; text-align:center; padding-top:50px;"><h1>Enhanced Verification... (Level 2)</h1><p>Your activity requires an additional security check. This may take a few moments.</p><div id="loader" style="margin:20px;">⚙️ Initializing combined verification...</div><script><!-- FINGERPRINT_SOLVER_SCRIPT --></script><script><!-- FINGERPRINT_CHALLENGE_SCRIPT --></script><!-- FINGERPRINT_TRAPS --></body></html>';
|
|
1323
1172
|
$customTemplatePath = $securityConfig['challengePagePath'] ?? null;
|
|
@@ -1332,4 +1181,37 @@ JS;
|
|
|
1332
1181
|
$htmlTemplate
|
|
1333
1182
|
);
|
|
1334
1183
|
}
|
|
1184
|
+
|
|
1185
|
+
private static function logError(string $message): void
|
|
1186
|
+
{
|
|
1187
|
+
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Challenge diagnostic logging
|
|
1188
|
+
error_log($message);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Safely retrieves and sanitizes a value from the $_SERVER superglobal.
|
|
1193
|
+
* Uses WordPress functions if available, otherwise falls back to basic PHP sanitization.
|
|
1194
|
+
*
|
|
1195
|
+
* @param string $key The key to retrieve from $_SERVER.
|
|
1196
|
+
* @param mixed $default The default value to return if the key is not found.
|
|
1197
|
+
* @return mixed The sanitized value.
|
|
1198
|
+
*/
|
|
1199
|
+
private static function getSanitizedServerVar(string $key, $default = '')
|
|
1200
|
+
{
|
|
1201
|
+
if (!isset($_SERVER[$key])) {
|
|
1202
|
+
return $default;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Unslashed and sanitized immediately below
|
|
1206
|
+
$value = $_SERVER[$key];
|
|
1207
|
+
|
|
1208
|
+
if (function_exists('wp_unslash')) {
|
|
1209
|
+
$value = wp_unslash($value);
|
|
1210
|
+
}
|
|
1211
|
+
if (function_exists('sanitize_text_field')) {
|
|
1212
|
+
return sanitize_text_field($value);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
return is_scalar($value) ? (string) $value : '';
|
|
1216
|
+
}
|
|
1335
1217
|
}
|