Br rate limiting
Skill Lonsdale201/wp-agent-skills/better-route/br-rate-limiting
A community-maintained collection of agent skills for WordPress plugin and theme development.
npx -y skills add Lonsdale201/wp-agent-skills --skill br-rate-limitingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 21 stars21 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Configure better-route 1.1 RateLimitMiddleware with atomic fixed-window storage. Use for WpObjectCacheRateLimiter, TransientRateLimiter, persistent external object cache checks, wp_cache_incr, MySQL named locks, identity/native WordPress/IP keys, trusted proxies, Retry-After and X-RateLimit headers, custom key resolvers, or diagnosing shared guest buckets and race-prone rate limiting.
SKILL.md
5.6 KB, as published. Nobody here has run it
better-route: rate limiting
Use a fixed-window limiter with an atomic backend. Pick the backend from deployment capabilities; do not silently fall back from an atomic store to a racy read/modify/write.
Persistent object cache
use BetterRoute\Middleware\RateLimit\RateLimitMiddleware;
use BetterRoute\Middleware\RateLimit\WpObjectCacheRateLimiter;
$rateLimit = new RateLimitMiddleware(
limiter: new WpObjectCacheRateLimiter(group: 'myapp_rate_limit'),
limit: 60,
windowSeconds: 60,
);
$router->get('/account', $handler)
->protectedByMiddleware('bearerAuth')
->middleware([$auth, $rateLimit]);
WpObjectCacheRateLimiter requires:
- WordPress cache functions;
wp_using_ext_object_cache() === truewhen that function exists;wp_cache_incr();- a backend whose increment actually behaves atomically.
Construction or a failed increment throws. Use it with a verified Redis/Memcached-style persistent backend, not WordPress's request-local default object cache.
Transient backend
use BetterRoute\Middleware\RateLimit\TransientRateLimiter;
$rateLimit = new RateLimitMiddleware(
limiter: new TransientRateLimiter(),
limit: 20,
windowSeconds: 60,
);
In default WordPress mode, TransientRateLimiter wraps the transient read/modify/write in a MySQL GET_LOCK/RELEASE_LOCK critical section. It requires global $wpdb and may throw when the lock cannot be acquired or state cannot be persisted.
If custom getTransient/setTransient callbacks are injected, also inject a real synchronize callback when requests can run concurrently. Without it, the custom mode executes unsynchronized.
Default key in 1.1
The default key deeply canonicalizes the route and the first available identity:
- auth middleware user ID;
- auth subject;
- explicit context/native logged-in WordPress user ID;
- HMAC key identity;
- resolved client IP;
guestonly when no identity or IP is available.
This means cookie/application-password/native WordPress users get per-user buckets even without an attributes['auth'] entry. Anonymous callers normally get per-IP rather than one global guest bucket.
Run auth before rate limiting when token identity should win over IP:
->middleware([$auth, $rateLimit])
Trusted client IP
Use TrustedProxyClientIpResolver behind proxies:
use BetterRoute\Middleware\Network\TrustedProxyClientIpResolver;
$ipResolver = new TrustedProxyClientIpResolver(
trustedProxyCidrs: ['10.0.0.0/24', '2001:db8:1234::/48'],
forwardedHeaders: ['CF-Connecting-IP', 'X-Forwarded-For'],
);
$rateLimit = new RateLimitMiddleware(
limiter: $limiter,
limit: 60,
windowSeconds: 60,
clientIpResolver: $ipResolver,
);
The resolver reads a forwarded header only when immediate REMOTE_ADDR is trusted. For hop lists it walks right-to-left and returns the closest untrusted address, avoiding a client-forged leftmost value. Keep provider CIDRs current.
Response contract
Allowed responses receive:
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
Denied requests return 429 rate_limited, the same rate-limit headers, and Retry-After calculated from reset time. Browser clients must include Retry-After in CORS exposedHeaders if JavaScript needs it.
The middleware preserves headers on Better Route responses, WP_REST_Response, and raw array/scalar results.
Custom keys
$rateLimit = new RateLimitMiddleware(
limiter: $limiter,
limit: 100,
windowSeconds: 60,
keyResolver: static fn ($context): string => hash('sha256', json_encode([
'route' => $context->routePath,
'tenant' => current_tenant_id(),
'user' => get_current_user_id(),
], JSON_THROW_ON_ERROR)),
);
Include route/tenant/identity deliberately and use unambiguous structured encoding. A constant global key lets one caller exhaust the bucket for everyone.
Review checklist
- Verify backend atomicity under concurrency.
- Run auth before the limiter for per-token/user limits.
- Configure trusted proxy CIDRs before trusting forwarded headers.
- Test first, last allowed, and first denied request; assert remaining/reset/retry headers.
- Test an anonymous caller from two IPs and two authenticated users.
- Monitor MySQL named-lock or cache increment failures; they are availability failures, not permission denials.
- Use upstream/CDN protection as well; PHP-level rate limiting is not volumetric DDoS mitigation.
Related skills
- Use
br-network-securityfor proxy/CIDR rules. - Use
br-auth-middlewarefor identity ordering. - Use
br-cors-public-clientto expose rate-limit headers.
References
- Verified source paths:
src/Middleware/RateLimit/RateLimitMiddleware.phpsrc/Middleware/RateLimit/WpObjectCacheRateLimiter.phpsrc/Middleware/RateLimit/TransientRateLimiter.phpsrc/Support/RequestIdentity.phpsrc/Middleware/Network/TrustedProxyClientIpResolver.php