feat(rate-limit): minute-bucket middleware with per-key and per-org counters (1.5c)#36
Open
Shaivpidadi wants to merge 2 commits intodevfrom
Open
feat(rate-limit): minute-bucket middleware with per-key and per-org counters (1.5c)#36Shaivpidadi wants to merge 2 commits intodevfrom
Shaivpidadi wants to merge 2 commits intodevfrom
Conversation
…ounters (1.5c)
Replaces the sliding-window-log limiter with minute-bucket sliding-window
counters for four dimensions: per-key requests, per-key tokens, per-org
requests, per-org tokens. Counters live under `{dim}:{scope}:{id}:{minute}`
keys with a 2-minute TTL so the previous bucket contributes to the
sliding-window weight.
The limiter now runs as FastAPI middleware (`app.rate_limit_middleware`)
before route handlers, so unauthenticated flood attempts cannot escape the
counter by bailing in `require_api_key`. All responses carry
`X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`
reflecting the most restrictive dimension; denied requests return 429 with
`Retry-After`.
Cipher review scope (precheck#31, non-blocking #4):
- `REDIS_URL` posture validator: non-debug environments must use
`rediss://` (TLS) and carry a password. Plaintext/passwordless Redis is
debug-only.
- Multi-replica quota-bypass: resolved by defaulting to `fail-closed` on
Redis outage. The middleware returns 503 `rate limiter unavailable`
rather than silently falling back to a per-replica in-memory counter
that multiplies the effective quota by N. Operators can opt into
`RATE_LIMIT_FAIL_MODE=open` (accept quota bypass) or `local` (debug-only
per-replica fallback); `Settings` rejects `local` outside `DEBUG=true`.
Behavior is documented in `precheck/PROJECT_SPECS.md#rate-limiting`.
Refs: 62aac781-1312-4184-b5e3-39ce37afddb7
- black/isort formatting on rate_limit middleware + tests - cast SQLAlchemy Column[str] to str for org_id return type
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements §1.5c rate-limit middleware on top of the §1.5b Redis wiring.
counters before route handlers. Removes the in-route rate-limit calls
from `precheck` / `postcheck`.
headers on every authenticated response; `Retry-After` on 429s.
consume quota.
Cipher security scope (precheck#31 non-blocking #4)
`REDIS_URL` outside debug mode unless it uses the `rediss://` TLS
scheme and carries a password. Plaintext is debug-only.
fail-closed: when Redis is configured but unreachable the middleware
returns HTTP 503 `rate limiter unavailable` rather than silently
falling back to a per-replica in-memory counter (which would multiply
the effective quota by N replicas). Operators can opt into
`RATE_LIMIT_FAIL_MODE=open` or `local`; `local` is rejected
outside `DEBUG=true`.
GovernsAI Tracker issue
GOV-1853 / 62aac781-1312-4184-b5e3-39ce37afddb7
Reviewers
Tagging Nexus (code quality) and Cipher (security/arch) — both approvals required.
Test plan
increment, minute-window reset, partial-window sliding weight, per-key
vs per-org independence, token cost enforcement, fail-closed /
fail-open / fail-local Redis outage behavior, and clear().
TestClient covering 100-req/min limit, Retry-After header, all three
X-RateLimit-* headers on success, remaining decrement across requests,
minute-bucket roll admitting new requests, token-limit 429, and health
bypass.
scheme rejection, passwordless rejection, debug-mode plaintext
acceptance, and `RATE_LIMIT_FAIL_MODE` validation.
213 passed, 9 skipped.