throttle-guard
Skill0 starsChanged 4 months agoMITView on GitHub
Audits whether a rate limit actually holds across a horizontally-scaled fleet instead of only looking like it does on one box — an in-memory counter/token-bucket that's per-process, so N running instances silently let N× the configured limit through; a "shared" Redis/Memcached counter whose check-then-increment isn't atomic, so concurrent requests across instances still race past the limit the same way a local counter would; and a limit enforced at one entry point (the API gateway) that a second path (an internal service call, a background worker, a websocket handler, a newer endpoint) never goes through at all. Use when adding or reviewing rate limiting on a service that runs more than one instance/pod/replica, right after a limit that should have blocked abuse clearly let more through than configured, or when asked "does our rate limit actually hold across instances", "why did this limit let N times what we set through", "is this counter shared or per-process", or "will scaling up break our rate limiting".
In this repository46 other files it gives its agents.
- atomic-ioSkill
- backfill-pilotSkill
- backpressure-guardSkill
- blast-guardSkill
- changelogSkill
- cipher-guardSkill
- clipboard-guardSkill
- clock-sweepSkill
- codebase-tourSkill
- consent-guardSkill
- devlogSkill
- dormancy-guardSkill
- env-detectiveSkill
- erasure-guardSkill
- export-guardSkill
- failover-guardSkill
- git-rescueSkill
- import-guardSkill
- job-wardenSkill
- migration-guardSkill
- overwrite-guardSkill
- pagination-guardSkill
- portability-auditSkill
- pref-guardSkill
- proration-guardSkill
- push-guardSkill
- quote-guardSkill
- reaccept-guardSkill
- readme-forgeSkill
- recall-guardSkill
- residency-guardSkill
- restore-guardSkill
- resync-guardSkill
- rollout-guardSkill
- rotation-guardSkill
- seat-guardSkill
- secret-spillSkill
- security-sweepSkill
- selectall-guardSkill
- ship-itSkill
- skew-checkSkill
- stale-guardSkill
- storm-guardSkill
- sunset-guardSkill
- tombstoneSkill
- trail-guardSkill
The file
Files
throttle-guard
A rate limit is usually written and tested against one running process, where an in-memory counter works perfectly — it's correct, fast, and every test that hits the endpoint from a single process sees it trip exactly on schedule. Then the service gets a second instance for availability or load, and the limit silently becomes a per-instance limit instead of the fleet-wide one everyone still believes is enforced: a "100 requests/minute" cap behind 4 instances is actually a 400 requests/minute cap, split however the load balancer happens to route traffic, and nothing about the code changed — only the deploy topology did. This is the specific trap: the bug is invisible in code review unless you ask the one question the code itself never answers — "where does this process's counter live, and is anything else running the same code sharing it?"
Step 1: is the counter actually shared, or does it just look like it is?
Find where the limiter's state lives. An in-process data structure — a plain dict/map, a library's default in-memory store (many rate-limiting middlewares default to this), a counter held in a global variable or closure — is scoped to exactly one process. If the service runs as more than one instance, pod, worker, or container (check the deploy config — a Kubernetes Deployment with replicas > 1, an autoscaling group, multiple PM2/cluster workers on one box, several app servers behind a load balancer), each one is independently enforcing the same configured number against only the slice of traffic it personally sees. The aggregate limit the system actually enforces is the configured limit multiplied by however many instances happen to be running at that moment — a number that changes every time autoscaling adds or removes an instance, making the real limit not just wrong but non-deterministic.
The fix people reach for is a shared external store (Redis, Memcached, a database table), and its mere presence is necessary but not sufficient — move to Step 2 before treating a shared store as proof the limit is actually fleet-wide. Also check what happens when that store is unreachable: does the limiter fail open (skip the check, let the request through — safe for availability, but silently disables the limit exactly when infrastructure is already stressed) or fail closed (block everything — safe for the limit, but turns a cache/Redis blip into a full outage)? Neither answer is automatically wrong, but an unexamined default (most client libraries fail open silently, swallowing the connection error) is a finding: whoever owns this limit should have chosen that tradeoff on purpose, not inherited it from a library default.
Step 2: is the shared counter's check-and-increment actually atomic?
A shared store closes the per-instance gap but opens a narrower version of the same race if the limiter does a separate read, compare, and write instead of one atomic operation: instance A reads the current count, instance B reads the same count before A writes back, both see room under the limit, both increment — two requests both pass at the exact count where only one should have. Look for the actual operation against the shared store:
- An atomic primitive (Redis
INCR/INCRBYwithEXPIREset once, a Lua script that reads-checks-writes in one round trip, a sliding-window algorithm implemented as a single atomic command, a databaseUPDATE ... WHERE count < limitchecked by rows-affected) closes the race. - A
GETfollowed by application-code comparison followed by a separateSET/INCRcall is the same check-then-act gap Step 1's per-instance counter has, just moved one layer out — the shared store made the data shared without making the operation safe under concurrency.
This matters more under load, not less — the race only costs the limit's integrity when two requests actually land close enough in time to interleave, which is exactly the burst-traffic scenario a rate limit exists to survive.
Step 3: does every path that should share this limit actually enforce through the same mechanism?
A rate limit is easy to add correctly once and then silently stop covering the thing it's named for, because later additions to the system have no reason to know the limit exists unless they're the one who has to re-derive it. Enumerate every way the limited action can actually happen, not just the one the limiter was originally written against: an API gateway/middleware limit that only wraps the public REST routes while an internal gRPC or service-to-service call hits the same downstream resource directly; a GraphQL endpoint added after the REST rate limiter and never wired into it, so the same mutation is capped on one surface and unlimited on the other; a background worker or queue consumer that calls the same rate-limited third-party API or expensive operation without going through the HTTP layer the limiter sits in front of; a websocket handler whose per-connection message rate was never brought under the same cap as the equivalent REST calls. For each path found, confirm it increments the same counter key (same store, same key naming) Step 1/2 verified is correctly shared and atomic — a second limiter that was reimplemented separately for a new path is exactly as likely to have reintroduced Step 1's per-instance gap or Step 2's non-atomic race, even if the original path is airtight.
Report
BLOCK for: an in-memory/per-process limiter backing a limit that's documented or assumed to be fleet-wide, on a service that runs more than one instance (Step 1); a shared-store limiter whose increment is a separate read-compare-write instead of one atomic operation, under real concurrent load (Step 2); or any code path that performs the limited action without going through the same counter/key the primary enforcement path uses (Step 3). CAUTION for: a shared, atomic limiter whose fail-open/fail-closed behavior on store unavailability was never a deliberate decision; or a newer path that calls what looks like the same shared check but through a separately vendored/forked copy of the limiter logic that could drift from the original. Name the exact store (or absence of one), the exact operation issued against it, and the exact paths that do or don't share it — "rate limiting looks fine" is not a finding; either the counter is shared, the increment is atomic, and every path that performs the action uses it, or it doesn't.
Boundaries
- Not
backpressure-guard's territory — that skill audits whether a stalled consumer in a broadcast/fan-out loop stops delivery to everyone else or grows an unbounded queue; this skill audits whether a rate limit's own enforcement holds across a fleet, independent of any backpressure concern. - Not
storm-guard's territory — that skill audits whether a fleet surviving a mass-reconnect event overwhelms auth/DB with no coordination; this skill audits steady-state rate-limit correctness, not a reconnection storm specifically (though an unenforced per-instance limit can certainly make a storm worse). - Doesn't design or pick a rate-limiting algorithm (token bucket vs. sliding window vs. fixed window) from scratch — it audits whether whatever algorithm is already chosen is actually enforced coherently across instances, not which algorithm best fits a given traffic shape.
- Can't observe real fleet-scale traffic or confirm an actual historical overage from source alone — it identifies whether the code's own state model and operations are capable of letting more through than configured, and says plainly when confirming a real incident needs production metrics (actual request counts against actual instance counts over the incident window), not a code read.
- Doesn't cover limits enforced entirely by infrastructure outside the application's own code (a cloud load balancer's or CDN's built-in rate limiting, a WAF rule) — those have their own distributed-enforcement guarantees from the vendor; this skill audits limits the application code itself is responsible for keeping correct.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

