PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.9.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.9.0
2.9.0 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 All 50 releases
← All changes | includes/api/traits/trait-rate-limiter.php +20 -27 1.0.1 → 2.9.0 View file →
@@ -55,9 +55,9 @@
55 55 * @param string $endpoint_name Endpoint identifier
56 56 * @param int $user_id User ID (0 for anonymous)
57 57 * @return true|WP_Error True if within limits, WP_Error if exceeded
58 58 */
59 - protected function check_rate_limit(string $endpoint_name, int $user_id = 0): bool|WP_Error {
59 + protected function check_rate_limit(string $endpoint_name, int $user_id = 0) {
60 60 // Get user identifier (IP for anonymous, user ID for authenticated)
61 61 $identifier = $user_id > 0 ? "user_{$user_id}" : $this->get_client_ip();
62 62
63 63 // Check minute-based rate limit
@@ -98,38 +98,31 @@
98 98 *
99 99 * @return string Client IP address
100 100 */
101 101 private function get_client_ip(): string {
102 - // Check for various headers that might contain the real IP
103 - $headers = [
104 - 'HTTP_CF_CONNECTING_IP', // Cloudflare
105 - 'HTTP_CLIENT_IP', // Proxy
106 - 'HTTP_X_FORWARDED_FOR', // Load balancer/proxy
107 - 'HTTP_X_FORWARDED', // Proxy
108 - 'HTTP_X_CLUSTER_CLIENT_IP', // Cluster
109 - 'HTTP_FORWARDED_FOR', // Proxy
110 - 'HTTP_FORWARDED', // Proxy
111 - 'REMOTE_ADDR' // Standard
112 - ];
102 + // Forwarded headers are client-controlled: trusting them by default
103 + // lets a caller mint a fresh rate-limit bucket per request. Only
104 + // consult them when the site opts in because a trusted proxy/CDN
105 + // sits in front and REMOTE_ADDR is the proxy, not the client.
106 + $trusted_headers = apply_filters('thinkrank_trusted_ip_headers', []);
113 107
114 - foreach ($headers as $header) {
115 - if (!empty($_SERVER[$header])) {
116 - $ip = $_SERVER[$header];
117 -
118 - // Handle comma-separated IPs (X-Forwarded-For can contain multiple IPs)
119 - if (strpos($ip, ',') !== false) {
120 - $ip = trim(explode(',', $ip)[0]);
121 - }
122 -
123 - // Validate IP address
124 - if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
125 - return $ip;
126 - }
108 + foreach ((array) $trusted_headers as $header) {
109 + if (empty($_SERVER[$header])) {
110 + continue;
127 111 }
112 + $ip = sanitize_text_field(wp_unslash($_SERVER[$header]));
113 +
114 + // X-Forwarded-For can contain a comma-separated chain.
115 + if (strpos($ip, ',') !== false) {
116 + $ip = trim(explode(',', $ip)[0]);
117 + }
118 +
119 + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
120 + return $ip;
121 + }
128 122 }
129 123
130 - // Fallback to REMOTE_ADDR even if it's a private IP
131 - return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
124 + return isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '0.0.0.0';
132 125 }
133 126
134 127 /**
135 128 * Get rate limit status for a user/endpoint