bool (default true, controls IP address hashing) */ class ABJ_404_Solution_PiiRedactor { /** @var ABJ_404_Solution_Functions */ private $f; /** * @param ABJ_404_Solution_Functions $functions */ public function __construct($functions) { $this->f = $functions; } /** * Redact PII from a text string. * * @param string $text The text to redact * @param array $options Optional configuration overrides * @return string Redacted text */ public function redact($text, $options = array()) { $redactIps = !isset($options['redact_ips']) || $options['redact_ips']; $text = $this->stripUrlQueryStrings($text); $text = $this->stripPathQueryStrings($text); $text = $this->redactAuthorizationHeaders($text); $text = $this->redactCookieValues($text); $text = $this->redactSensitiveFormFields($text); $text = $this->redactEmails($text); if ($redactIps) { $text = $this->redactIpv4($text); $text = $this->redactIpv6($text); } $text = $this->redactUsernames($text); $text = $this->redactDisplayNames($text); $text = $this->redactAbsolutePaths($text); $text = $this->redactDatabaseIdentifiers($text); $text = $this->redactLongTokens($text); $text = $this->redactNonces($text); return $text; } // ========================================================================= // URL / query-string stripping // ========================================================================= /** @param string $text @return string */ private function stripUrlQueryStrings(string $text): string { return preg_replace('/(https?:\/\/[^\s?]+)\?[^\s]*/', '$1', $text) ?? $text; } /** @param string $text @return string */ private function stripPathQueryStrings(string $text): string { return preg_replace('/(?maskEmailAdaptive($matches[0]); }, $text ) ?? $text; } // ========================================================================= // IP addresses // ========================================================================= /** @param string $text @return string */ private function redactIpv4(string $text): string { return preg_replace_callback( '/\b(?:\d{1,3}\.){3}\d{1,3}\b/', function ($matches) { return $this->f->md5lastOctet($matches[0]); }, $text ) ?? $text; } /** @param string $text @return string */ private function redactIpv6(string $text): string { return preg_replace_callback( '/(?f->md5lastOctet($matches[0]); }, $text ) ?? $text; } // ========================================================================= // Usernames and display names // ========================================================================= /** @param string $text @return string */ private function redactUsernames(string $text): string { return preg_replace_callback( '/\b(current\s+)?user(name)?:\s*(\S+)/i', function ($matches) { $prefix = $matches[1] . 'user' . $matches[2] . ': '; return $prefix . $this->maskTextAdaptive($matches[3]); }, $text ) ?? $text; } /** @param string $text @return string */ private function redactDisplayNames(string $text): string { return preg_replace_callback( '/\bdisplay\s+name:\s*([^\n,]+)/i', function ($matches) { return 'display name: ' . $this->maskTextAdaptive(trim($matches[1])); }, $text ) ?? $text; } // ========================================================================= // Absolute file paths // ========================================================================= /** @param string $text @return string */ private function redactAbsolutePaths(string $text): string { $wpMarkers = 'wp-content|wp-admin|wp-includes|wp-login\\.php|wp-config\\.php|wp-cron\\.php|wp-blog-header\\.php'; $text = preg_replace( '/(^|[\s\(])(\/[^\s\(]+?)\/(' . $wpMarkers . ')\b/i', '$1/$3', $text ) ?? $text; $text = preg_replace( '/\b[a-z]:\\\\[^\s]+\\\\(' . $wpMarkers . ')\b/i', '\\\\$1', $text ) ?? $text; return $text; } // ========================================================================= // Database identifiers (name + prefix) // ========================================================================= /** @param string $text @return string */ private function redactDatabaseIdentifiers(string $text): string { $dbname = $this->getActualDatabaseNameForRedaction(); if ($dbname !== '' && strlen($dbname) >= 3 && $dbname !== 'dbname') { $text = preg_replace( '/(?getActualPrefixForRedaction(); if ($prefix !== '' && $prefix !== 'wp_') { $text = preg_replace( '/(?maskTextAdaptive($email); } list($username, $fullDomain) = $parts; $domainParts = explode('.', $fullDomain); if (count($domainParts) > 1) { if (in_array(end($domainParts), array('uk', 'au', 'nz', 'za'))) { array_pop($domainParts); array_pop($domainParts); } else { array_pop($domainParts); } } $domain = implode('.', $domainParts); $usernameLen = strlen($username); if ($usernameLen <= 4) { $usernameVisible = 1; } elseif ($usernameLen <= 9) { $usernameVisible = 2; } else { $usernameVisible = 3; } $domainLen = strlen($domain); $domainVisible = max(1, (int)ceil($domainLen * 0.3)); $maskedUsername = substr($username, 0, $usernameVisible) . '***'; $maskedDomain = empty($domain) ? '' : substr($domain, 0, $domainVisible) . '***'; if (defined('AUTH_SALT')) { $hash = substr(md5(AUTH_SALT . $email), 0, 4); } else { $hash = substr(md5($email), 0, 4); } if (!empty($maskedDomain)) { return $maskedUsername . '@' . $maskedDomain . '-' . $hash; } return $maskedUsername . '@-' . $hash; } /** * @param string $text * @return string */ public function maskTextAdaptive($text) { if (empty($text)) { return $text; } $text = trim($text); $textLen = strlen($text); if ($textLen <= 4) { $visible = 1; } elseif ($textLen <= 9) { $visible = 2; } else { $visible = 3; } $masked = substr($text, 0, $visible) . '***'; if (defined('AUTH_SALT')) { $hash = substr(md5(AUTH_SALT . $text), 0, 4); } else { $hash = substr(md5($text), 0, 4); } return $masked . '-' . $hash; } // ========================================================================= // Database context helpers // ========================================================================= /** @return string */ private function getActualPrefixForRedaction() { global $wpdb; if (isset($wpdb) && is_object($wpdb) && isset($wpdb->prefix) && is_string($wpdb->prefix)) { return $wpdb->prefix; } return ''; } /** @return string */ private function getActualDatabaseNameForRedaction() { global $wpdb; if (isset($wpdb) && is_object($wpdb) && isset($wpdb->dbname) && is_string($wpdb->dbname)) { return $wpdb->dbname; } if (defined('DB_NAME')) { $name = constant('DB_NAME'); if (is_string($name) && $name !== '') { return $name; } } return ''; } }