@anonympins/fingerprint 0.8.0 → 0.8.2

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.
@@ -236,6 +236,31 @@ class ChallengeUtils
236
236
  return $activePeers[array_rand($activePeers)];
237
237
  }
238
238
 
239
+ public static function verifyEd25519Signature(string $message, string $signatureHex, array $config = []): bool
240
+ {
241
+ if (empty($message) || empty($signatureHex)) {
242
+ return false;
243
+ }
244
+ $publicKey = $config['ed25519_public_key'] ?? Env::get('ED25519_PUBLIC_KEY');
245
+ if (empty($publicKey)) {
246
+ return false;
247
+ }
248
+ try {
249
+ $cleanKey = str_replace('\n', "\n", $publicKey);
250
+ $pubKeyObj = openssl_pkey_get_public($cleanKey);
251
+ if (!$pubKeyObj) {
252
+ return false;
253
+ }
254
+ $sigBytes = hex2bin($signatureHex);
255
+ if ($sigBytes === false) {
256
+ return false;
257
+ }
258
+ return openssl_verify($message, $sigBytes, $pubKeyObj, null) === 1;
259
+ } catch (\Throwable $e) {
260
+ return false;
261
+ }
262
+ }
263
+
239
264
  /**
240
265
  * Handles cooperative peer-to-peer operations (federation threat intel sharing,
241
266
  * node registration, peer discovery, WebRTC signaling, block requests/responses).
@@ -261,7 +286,7 @@ class ChallengeUtils
261
286
  $peers = $config['federatedPeers'] ?? [];
262
287
  if (!empty($peers)) {
263
288
  $allowedHosts = array_map(function ($url) {
264
- // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
289
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Fallback for standalone PHP environments
265
290
  $host = function_exists('wp_parse_url') ? wp_parse_url($url, PHP_URL_HOST) : parse_url($url, PHP_URL_HOST);
266
291
  return !empty($host) ? $host : $url;
267
292
  }, $peers);
@@ -289,18 +314,8 @@ class ChallengeUtils
289
314
  $msg = "{$timestamp}:{$zkpY}";
290
315
 
291
316
  if (!empty($sigEd25519)) {
292
- $publicKey = $config['ed25519_public_key'] ?? Env::get('ED25519_PUBLIC_KEY');
293
- if (!$publicKey) {
294
- return ['error' => 'Missing public key for asymmetric verification'];
295
- }
296
- try {
297
- $cleanKey = str_replace('\n', "\n", $publicKey);
298
- $pubKeyObj = openssl_pkey_get_public($cleanKey);
299
- if (!$pubKeyObj || openssl_verify($msg, hex2bin($sigEd25519), $pubKeyObj, null) !== 1) {
300
- return ['error' => 'Invalid asymmetric federation signature'];
301
- }
302
- } catch (\Throwable $e) {
303
- return ['error' => 'Asymmetric signature verification failed'];
317
+ if (!self::verifyEd25519Signature($msg, $sigEd25519, $config)) {
318
+ return ['error' => 'Invalid asymmetric federation signature'];
304
319
  }
305
320
  } elseif (!empty($sigHmac)) {
306
321
  $secret = $params['federationSecret'] ?? $config['federationSecret'] ?? self::getPowSecret();
@@ -330,6 +345,51 @@ class ChallengeUtils
330
345
  return ['status' => 'synchronized', 'banned' => false, 'reportsCount' => count($reportedPeers)];
331
346
  }
332
347
 
348
+ if ($op === 'share_whitelist') {
349
+ $peers = $config['federatedPeers'] ?? [];
350
+ if (!empty($peers)) {
351
+ $allowedHosts = array_map(function ($url) {
352
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Fallback for standalone PHP environments
353
+ $host = function_exists('wp_parse_url') ? wp_parse_url($url, PHP_URL_HOST) : parse_url($url, PHP_URL_HOST);
354
+ return !empty($host) ? $host : $url;
355
+ }, $peers);
356
+
357
+ if (!in_array($clientIp, $allowedHosts, true)) {
358
+ return ['error' => 'Unauthorized federation sender IP'];
359
+ }
360
+ }
361
+
362
+ $entry = $params['entry'] ?? '';
363
+ $entryType = $params['entry_type'] ?? 'ip';
364
+ $headers = function_exists('getallheaders') ? array_change_key_case(getallheaders(), CASE_LOWER) : [];
365
+ $sigEd25519 = $params['signature_ed25519'] ?? $headers['x-federation-signature-ed25519'] ?? self::getSanitizedServerVar('HTTP_X_FEDERATION_SIGNATURE_ED25519', '');
366
+ $timestamp = (int)($params['timestamp'] ?? $headers['x-federation-timestamp'] ?? self::getSanitizedServerVar('HTTP_X_FEDERATION_TIMESTAMP', 0));
367
+ $ttl = (int)($params['ttl'] ?? 86400);
368
+
369
+ $cleanEntry = trim($entry);
370
+ if ($cleanEntry === '' || empty($sigEd25519) || empty($timestamp)) {
371
+ return ['error' => 'Missing required parameters for share_whitelist'];
372
+ }
373
+
374
+ if ($cleanEntry === '*' || $cleanEntry === '0.0.0.0/0' || $cleanEntry === '::/0') {
375
+ return ['error' => 'Permissive wildcard entries are prohibited'];
376
+ }
377
+
378
+ $now = (int)(microtime(true) * 1000);
379
+ if (abs($now - $timestamp) > 300000) {
380
+ return ['error' => 'Timestamp expired or clock skew too high'];
381
+ }
382
+
383
+ $msg = "{$timestamp}:whitelist:{$entryType}:{$cleanEntry}";
384
+ if (!self::verifyEd25519Signature($msg, $sigEd25519, $config)) {
385
+ return ['error' => 'Invalid Ed25519 signature for whitelist synchronization'];
386
+ }
387
+
388
+ $boundedTtl = min(604800, max(60, $ttl));
389
+ $store->set("federated-whitelist:{$entryType}:{$cleanEntry}", true, $boundedTtl);
390
+ return ['status' => 'whitelist_synchronized', 'entry' => $cleanEntry, 'entryType' => $entryType, 'ttl' => $boundedTtl];
391
+ }
392
+
333
393
  $nodeId = $params['node_id'] ?? '';
334
394
  if (empty($nodeId)) {
335
395
  return ['error' => 'Missing node_id'];
@@ -353,19 +413,19 @@ class ChallengeUtils
353
413
  $expectedMsg = "{$clientSecret}:find_peer:{$nodeId}";
354
414
  break;
355
415
  case 'webrtc_signal':
356
- $expectedMsg = "{$clientSecret}:webrtc_signal:{$nodeId}:" . ($params['target_peer_id'] ?? '') . ":" . ($params['signal_type'] ?? '') . ":" . ($params['signal_data'] ?? '');
416
+ $expectedMsg = "{$clientSecret}:webrtc_signal:{$nodeId}:" . (isset($params['target_peer_id']) ? self::sanitizeString($params['target_peer_id']) : '') . ":" . (isset($params['signal_type']) ? self::sanitizeString($params['signal_type']) : '') . ":" . (isset($params['signal_data']) ? self::sanitizeString($params['signal_data']) : '');
357
417
  break;
358
418
  case 'poll_signals':
359
419
  $expectedMsg = "{$clientSecret}:poll_signals:{$nodeId}";
360
420
  break;
361
421
  case 'request_peer_block':
362
- $expectedMsg = "{$clientSecret}:request_peer_block:{$nodeId}:" . ($params['peer_id'] ?? '') . ":" . ($params['block_idx'] ?? '0') . ":" . ($params['req_id'] ?? '');
422
+ $expectedMsg = "{$clientSecret}:request_peer_block:{$nodeId}:" . (isset($params['peer_id']) ? self::sanitizeString($params['peer_id']) : '') . ":" . ($params['block_idx'] ?? '0') . ":" . (isset($params['req_id']) ? self::sanitizeString($params['req_id']) : '');
363
423
  break;
364
424
  case 'poll_requests':
365
425
  $expectedMsg = "{$clientSecret}:poll_requests:{$nodeId}";
366
426
  break;
367
427
  case 'respond_block':
368
- $expectedMsg = "{$clientSecret}:respond_block:{$nodeId}:" . ($params['requester_id'] ?? '') . ":" . ($params['req_id'] ?? '') . ":" . ($params['block_data'] ?? '');
428
+ $expectedMsg = "{$clientSecret}:respond_block:{$nodeId}:" . (isset($params['requester_id']) ? self::sanitizeString($params['requester_id']) : '') . ":" . (isset($params['req_id']) ? self::sanitizeString($params['req_id']) : '') . ":" . (isset($params['block_data']) ? self::sanitizeString($params['block_data']) : '');
369
429
  break;
370
430
  case 'poll_response':
371
431
  $expectedMsg = "{$clientSecret}:poll_response:{$nodeId}:" . ($params['req_id'] ?? '');
@@ -383,7 +443,7 @@ class ChallengeUtils
383
443
  switch ($op) {
384
444
  case 'register':
385
445
  $clientIp = self::getSanitizedServerVar('REMOTE_ADDR', '127.0.0.1');
386
- $seed = $params['seed'] ?? ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
446
+ $seed = isset($params['seed']) ? self::sanitizeString($params['seed']) : '';
387
447
  self::registerCooperativeNode($clientIp, $nodeId, $seed);
388
448
  return ['status' => 'registered'];
389
449
 
@@ -395,9 +455,9 @@ class ChallengeUtils
395
455
  return ['status' => 'no_peers'];
396
456
 
397
457
  case 'webrtc_signal':
398
- $targetPeerId = $params['target_peer_id'] ?? '';
399
- $signalType = $params['signal_type'] ?? '';
400
- $signalData = $params['signal_data'] ?? '';
458
+ $targetPeerId = isset($params['target_peer_id']) ? self::sanitizeString($params['target_peer_id']) : '';
459
+ $signalType = isset($params['signal_type']) ? self::sanitizeString($params['signal_type']) : '';
460
+ $signalData = isset($params['signal_data']) ? self::sanitizeString($params['signal_data']) : '';
401
461
  if (empty($targetPeerId) || empty($signalType) || empty($signalData)) {
402
462
  return ['error' => 'Invalid parameters'];
403
463
  }
@@ -420,9 +480,9 @@ class ChallengeUtils
420
480
  return ['status' => 'ok', 'signals' => $signals];
421
481
 
422
482
  case 'request_peer_block':
423
- $peerId = $params['peer_id'] ?? '';
483
+ $peerId = isset($params['peer_id']) ? self::sanitizeString($params['peer_id']) : '';
424
484
  $blockIdx = (int)($params['block_idx'] ?? 0);
425
- $requestId = $params['req_id'] ?? '';
485
+ $requestId = isset($params['req_id']) ? self::sanitizeString($params['req_id']) : '';
426
486
  if (empty($peerId) || empty($requestId)) {
427
487
  return ['error' => 'Invalid parameters'];
428
488
  }
@@ -444,9 +504,9 @@ class ChallengeUtils
444
504
  return ['requests' => $requests];
445
505
 
446
506
  case 'respond_block':
447
- $requesterId = $params['requester_id'] ?? '';
448
- $requestId = $params['req_id'] ?? '';
449
- $blockData = $params['block_data'] ?? '';
507
+ $requesterId = isset($params['requester_id']) ? self::sanitizeString($params['requester_id']) : '';
508
+ $requestId = isset($params['req_id']) ? self::sanitizeString($params['req_id']) : '';
509
+ $blockData = isset($params['block_data']) ? self::sanitizeString($params['block_data']) : '';
450
510
  if (empty($requesterId) || empty($requestId)) {
451
511
  return ['error' => 'Invalid parameters'];
452
512
  }
@@ -456,7 +516,7 @@ class ChallengeUtils
456
516
  return ['status' => 'delivered'];
457
517
 
458
518
  case 'poll_response':
459
- $requestId = $params['req_id'] ?? '';
519
+ $requestId = isset($params['req_id']) ? self::sanitizeString($params['req_id']) : '';
460
520
  $responseKey = "coop-mailbox:res:{$nodeId}:{$requestId}";
461
521
  $data = $store->get($responseKey);
462
522
  if ($data) {
@@ -634,28 +694,44 @@ class ChallengeUtils
634
694
  throw new \RuntimeException("Failed to extract Ed25519 public key.");
635
695
  }
636
696
 
637
- if ($outDir === '') {
638
- $outDir = dirname(__DIR__, 2) . '/config';
639
- }
640
-
641
- if (!is_dir($outDir)) {
642
- if (function_exists('wp_mkdir_p')) {
643
- wp_mkdir_p($outDir);
644
- } else {
645
- mkdir($outDir, 0755, true);
646
- }
647
- }
648
-
649
697
  $privName = $options['privateKeyName'] ?? 'issuer-private.pem';
650
698
  $pubName = $options['publicKeyName'] ?? 'issuer-public.pem';
651
699
 
652
- $privateKeyPath = rtrim($outDir, '/\\') . '/' . $privName;
653
- $publicKeyPath = rtrim($outDir, '/\\') . '/' . $pubName;
700
+ $privateKeyPath = '';
701
+ $publicKeyPath = '';
702
+
703
+ if ($outDir !== '') {
704
+ if (!is_dir($outDir)) {
705
+ if (function_exists('wp_mkdir_p')) {
706
+ wp_mkdir_p($outDir);
707
+ } else {
708
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir -- Standalone filesystem fallback
709
+ mkdir($outDir, 0755, true);
710
+ }
711
+ }
712
+ $privateKeyPath = rtrim($outDir, '/\\') . '/' . $privName;
713
+ $publicKeyPath = rtrim($outDir, '/\\') . '/' . $pubName;
714
+
715
+ global $wp_filesystem;
716
+ if (empty($wp_filesystem) && defined('ABSPATH')) {
717
+ require_once ABSPATH . 'wp-admin/includes/file.php';
718
+ WP_Filesystem();
719
+ }
654
720
 
655
- file_put_contents($privateKeyPath, $privateKeyPem);
656
- @chmod($privateKeyPath, 0600);
657
- file_put_contents($publicKeyPath, $publicKeyPem);
658
- @chmod($publicKeyPath, 0644);
721
+ if (!empty($wp_filesystem) && is_object($wp_filesystem)) {
722
+ $wp_filesystem->put_contents($privateKeyPath, $privateKeyPem, 0600);
723
+ $wp_filesystem->put_contents($publicKeyPath, $publicKeyPem, 0644);
724
+ } else {
725
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Standalone filesystem fallback
726
+ file_put_contents($privateKeyPath, $privateKeyPem);
727
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- Standalone filesystem fallback
728
+ @chmod($privateKeyPath, 0600);
729
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Standalone filesystem fallback
730
+ file_put_contents($publicKeyPath, $publicKeyPem);
731
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- Standalone filesystem fallback
732
+ @chmod($publicKeyPath, 0644);
733
+ }
734
+ }
659
735
 
660
736
  return [
661
737
  'privateKeyPath' => $privateKeyPath,
@@ -1294,20 +1370,25 @@ class ChallengeUtils
1294
1370
  */
1295
1371
  private static function getPowSolverCode(): string
1296
1372
  {
1373
+ if (defined('ANONYMPINS_BOT_MITIGATION_DIR')) {
1374
+ $wpPluginSolver = rtrim(ANONYMPINS_BOT_MITIGATION_DIR, '/\\') . '/assets/pow.solver.inline.js';
1375
+ if (file_exists($wpPluginSolver)) {
1376
+ return (string)file_get_contents($wpPluginSolver);
1377
+ }
1378
+ } elseif (defined('WP_PLUGIN_DIR')) {
1379
+ $wpPluginSolver = WP_PLUGIN_DIR . '/anonympins-bot-mitigation-pow/assets/pow.solver.inline.js';
1380
+ if (file_exists($wpPluginSolver)) {
1381
+ return (string)file_get_contents($wpPluginSolver);
1382
+ }
1383
+ }
1384
+
1297
1385
  $candidates = [
1298
1386
  __DIR__ . '/../../js/pow.solver.inline.js',
1299
1387
  __DIR__ . '/../assets/pow.solver.inline.js',
1300
1388
  __DIR__ . '/../js/pow.solver.inline.js',
1301
- dirname(__DIR__, 2) . '/assets/pow.solver.inline.js',
1302
- dirname(__DIR__, 2) . '/js/pow.solver.inline.js',
1303
- dirname(__DIR__, 1) . '/assets/pow.solver.inline.js',
1389
+ dirname(__DIR__) . '/assets/pow.solver.inline.js',
1390
+ dirname(__DIR__) . '/js/pow.solver.inline.js',
1304
1391
  ];
1305
- if (defined('ABSPATH')) {
1306
- $wpPluginSolver = ABSPATH . 'wp-content/plugins/fingerprint-anti-bot/assets/pow.solver.inline.js';
1307
- if (file_exists($wpPluginSolver)) {
1308
- return file_get_contents($wpPluginSolver) ?: '';
1309
- }
1310
- }
1311
1392
  foreach ($candidates as $solverPath) {
1312
1393
  if (file_exists($solverPath)) {
1313
1394
  return file_get_contents($solverPath) ?: '';
@@ -1368,7 +1449,7 @@ class ChallengeUtils
1368
1449
  . '}' . "\n"
1369
1450
  . 'solve();';
1370
1451
 
1371
- 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>";
1452
+ 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>";
1372
1453
  }
1373
1454
 
1374
1455
  /**
@@ -1467,7 +1548,7 @@ class ChallengeUtils
1467
1548
  . '}' . "\n"
1468
1549
  . 'solve();';
1469
1550
 
1470
- $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>';
1551
+ $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>';
1471
1552
  $customTemplatePath = $securityConfig['challengePagePath'] ?? null;
1472
1553
 
1473
1554
  if ($customTemplatePath && file_exists($customTemplatePath)) {
@@ -1493,6 +1574,31 @@ class ChallengeUtils
1493
1574
  error_log($message);
1494
1575
  }
1495
1576
 
1577
+ /**
1578
+ * Safely sanitizes a string input using WordPress functions if available, or fallback PHP functions.
1579
+ *
1580
+ * @param mixed $value
1581
+ * @return string
1582
+ */
1583
+ private static function sanitizeString($value): string
1584
+ {
1585
+ if (!is_scalar($value)) {
1586
+ return '';
1587
+ }
1588
+ $val = (string)$value;
1589
+ if (function_exists('wp_unslash')) {
1590
+ $val = wp_unslash($val);
1591
+ }
1592
+ if (function_exists('sanitize_text_field')) {
1593
+ return sanitize_text_field($val);
1594
+ }
1595
+ if (function_exists('wp_strip_all_tags')) {
1596
+ return trim(wp_strip_all_tags($val));
1597
+ }
1598
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- Standalone fallback when running outside WordPress
1599
+ return trim(strip_tags($val));
1600
+ }
1601
+
1496
1602
  /**
1497
1603
  * Safely retrieves and sanitizes a value from the $_SERVER superglobal.
1498
1604
  * Uses WordPress functions if available, otherwise falls back to basic PHP sanitization.
@@ -1509,14 +1615,6 @@ class ChallengeUtils
1509
1615
 
1510
1616
  // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Unslashed and sanitized immediately below
1511
1617
  $value = $_SERVER[$key];
1512
-
1513
- if (function_exists('wp_unslash')) {
1514
- $value = wp_unslash($value);
1515
- }
1516
- if (function_exists('sanitize_text_field')) {
1517
- return sanitize_text_field($value);
1518
- }
1519
-
1520
- return is_scalar($value) ? (string) $value : '';
1618
+ return self::sanitizeString($value);
1521
1619
  }
1522
1620
  }
@@ -29,24 +29,44 @@ class DirectFingerprint
29
29
  */
30
30
  public function buildRequestContext(): RequestContext
31
31
  {
32
+ $sanitizeDeep = function ($data) use (&$sanitizeDeep) {
33
+ if (is_array($data)) {
34
+ $clean = [];
35
+ foreach ($data as $k => $v) {
36
+ $cleanKey = is_string($k) ? (function_exists('sanitize_text_field') && function_exists('wp_unslash') ? sanitize_text_field(wp_unslash($k)) : trim((string)$k)) : $k;
37
+ $clean[$cleanKey] = $sanitizeDeep($v);
38
+ }
39
+ return $clean;
40
+ }
41
+ if (is_string($data)) {
42
+ return function_exists('sanitize_text_field') && function_exists('wp_unslash') ? sanitize_text_field(wp_unslash($data)) : trim($data);
43
+ }
44
+ return $data;
45
+ };
46
+
47
+ $rawInput = (string)file_get_contents('php://input');
48
+ $decodedJson = !empty($rawInput) ? json_decode($rawInput, true) : null;
32
49
  // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Direct WAF inspection firewall before WP core logic
33
- $body = $_POST ?: json_decode(file_get_contents('php://input'), true);
34
- $headers = function_exists('getallheaders') ? getallheaders() : [];
50
+ $body = !empty($_POST) ? $sanitizeDeep($_POST) : (is_array($decodedJson) ? $sanitizeDeep($decodedJson) : null);
51
+ $rawHeaders = function_exists('getallheaders') ? getallheaders() : [];
52
+ $headers = is_array($rawHeaders) ? $sanitizeDeep($rawHeaders) : [];
35
53
 
36
54
  $remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '127.0.0.1';
37
55
  $rawUri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '/';
38
56
  // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Uses wp_parse_url when available
39
57
  $path = function_exists('wp_parse_url') ? (string)wp_parse_url($rawUri, PHP_URL_PATH) : (string)parse_url($rawUri, PHP_URL_PATH);
40
58
  $serverProtocol = isset($_SERVER['SERVER_PROTOCOL']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_PROTOCOL'])) : '1.1';
59
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
60
+ $query = isset($_GET) && is_array($_GET) ? $sanitizeDeep($_GET) : [];
61
+ $cookies = isset($_COOKIE) && is_array($_COOKIE) ? $sanitizeDeep($_COOKIE) : [];
41
62
 
42
63
  return new RequestContext(
43
64
  $remoteAddr,
44
65
  !empty($path) ? $path : '/',
45
66
  $headers,
46
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
47
- $_GET,
67
+ $query,
48
68
  $body,
49
- $_COOKIE,
69
+ $cookies,
50
70
  $serverProtocol
51
71
  );
52
72
  }