PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.7
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.7
1.3.7 1.3.6 1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 All 33 releases
← All changes | includes/modules/Mcp/Mcp_Rate_Limiter.php +31 -2 1.3.2 → 1.3.7 View file →
@@ -13,8 +13,14 @@
13 13 * window (transient-backed). At/after the threshold the IP is locked out
14 14 * for the window; a SUCCESSFUL auth clears the counter immediately so a
15 15 * legitimate client that fixed a typo isn't punished.
16 16 *
17 + * A new credential starts every client from zero (reset_all()). Failures
18 + * earned with the old token say nothing about the new one, and the client
19 + * that most needs to get through then is xSpeed Hub: its checks with the
20 + * old token are what locked it out, and its check of the new token is what
21 + * would clear the lock.
22 + *
17 23 * Threshold + window are overridable:
18 24 * - XSPEED_MCP_MAX_FAILS / XSPEED_MCP_LOCKOUT_SECONDS constants, and
19 25 * - the `xspeed_mcp_rate_limit` filter ( [ max_fails, lockout_seconds ] ).
20 26 *
@@ -31,8 +37,11 @@
31 37
32 38 /** Transient key prefix; the client-IP hash is appended. */
33 39 private const PREFIX = 'xspeed_mcp_rl_';
34 40
41 + /** Option holding the lockout generation; part of every transient key. */
42 + private const GENERATION_OPTION = 'xspeed_mcp_rl_gen';
43 +
35 44 /** Default: lock out after this many failed attempts. */
36 45 private const DEFAULT_MAX_FAILS = 10;
37 46
38 47 /** Default: lockout / rolling-window length, in seconds. */
@@ -69,8 +78,20 @@
69 78 public static function clear(): void {
70 79 delete_transient( self::key() );
71 80 }
72 81
82 + /**
83 + * Clear every client's counter at once. Call when the site hands out a
84 + * credential: a valid attach, or a token sent to the Hub.
85 + *
86 + * The counters are per-IP transients, which cannot be listed, so this
87 + * moves every key to a new generation instead. The old transients expire
88 + * on their own within the window.
89 + */
90 + public static function reset_all(): void {
91 + update_option( self::GENERATION_OPTION, self::generation() + 1, false );
92 + }
93 +
73 94 /** Seconds a locked client must wait (approximate; the window length). */
74 95 public static function retry_after(): int {
75 96 return self::limits()[1];
76 97 }
@@ -82,11 +103,19 @@
82 103 $v = get_transient( self::key() );
83 104 return is_numeric( $v ) ? (int) $v : 0;
84 105 }
85 106
86 - /** Transient key bound to the (hashed) client IP. */
107 + /** Transient key bound to the (hashed) client IP and the generation. */
87 108 private static function key(): string {
88 - return self::PREFIX . md5( self::client_ip() );
109 + $generation = self::generation();
110 + $suffix = md5( self::client_ip() );
111 + return self::PREFIX . ( $generation > 0 ? $generation . '_' : '' ) . $suffix;
112 + }
113 +
114 + /** Current lockout generation (0 until the first reset_all()). */
115 + private static function generation(): int {
116 + $v = get_option( self::GENERATION_OPTION, 0 );
117 + return is_numeric( $v ) ? (int) $v : 0;
89 118 }
90 119
91 120 /**
92 121 * Resolve [ max_fails, lockout_seconds ] from constants, then filter.