feat: rate limit - #74
Draft
mmagician wants to merge 3 commits into
Draft
Conversation
- Move governor/http to workspace [dependencies], matching convention. - Return a trailers-only gRPC response with grpc-status 8 (RESOURCE_EXHAUSTED), which is the canonical mapping for rate-limit rejection. Previously we returned HTTP 429 with grpc-status 14 (UNAVAILABLE), which clients treat differently for retry/backoff purposes. - Gate X-Forwarded-For / X-Real-IP trust behind a new `trust_forwarded_headers` flag (default false). Trusting these headers unconditionally lets any direct client bypass the limiter by spoofing the header. The flag also removes the localhost fallback, which previously coalesced all unidentifiable traffic into one shared bucket. - Bucket IPv6 addresses by /64 prefix so a single routable prefix cannot rotate through 2^64 addresses to dodge the limiter. - Spawn a tokio task that periodically calls retain_recent() on the governor state store, preventing unbounded dashmap growth. - Use NonZeroU32 in RateLimitConfig and as the clap value type, so --rate-limit-rps 0 fails at parse time instead of silently becoming 1. - Minor: inline the opt_nonzero helper in main, log "disabled" rather than "0s" for the disabled tcp-keepalive case. - Add unit tests for extract_ip precedence, trust flag honored, IPv6 /64 bucketing, and an end-to-end check that over-quota requests are rejected.
- Instead of letting unidentified traffic through unlimited, key it to a single shared bucket (UNKNOWN_IP_BUCKET = 0.0.0.0) so it is still rate-limited. In practice the branch should not fire because tonic always attaches TcpConnectInfo — the fallback is defensive. - Bump retain_recent() interval from 60s to 300s. Entries are small and eviction eligibility depends on bucket replenishment, not wall-clock, so a 5-minute sweep is plenty.
Closed
This was referenced Aug 7, 2026
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.
Draft PR against
ev/fmt(PR #60) with the review fixes from #60 (comment) (review I posted locally).Summary
grpc-status: 8(RESOURCE_EXHAUSTED) instead of HTTP 429 withgrpc-status: 14(UNAVAILABLE). Clients apply very different retry policies to these two codes.trust_forwarded_headers(default false) +--rate-limit-trust-forwarded-headersCLI flag. Trusting these unconditionally let any direct client bypass the limiter by spoofing the header. Also removed theLOCALHOSTfallback that coalesced unidentifiable traffic into a shared bucket./64prefix before being keyed, so a single routable/64cannot rotate through 2^64 addresses to dodge the limiter.governor's dashmap state store is now periodically cleaned by a background tokio task callingretain_recent()every 60s, preventing unbounded memory growth.requests_per_second/burst_sizeare nowNonZeroU32in the config and on the CLI, so--rate-limit-rps 0fails at parse time instead of being silently clamped to 1.governorandhttpare now referenced via[workspace.dependencies]to match the rest of the crate.extract_ipprecedence + trust-flag gating, IPv6/64bucketing, and a smoke test that over-quota requests hitRESOURCE_EXHAUSTED.Test plan
cargo test --workspace --all-targets(12 tests pass, 7 new)make lint(fmt + fix + clippy + toml + workspace-lints)--rate-limit-rps+grpcurlto confirm theRESOURCE_EXHAUSTEDresponse shapeWorktree:
/home/marti/dev/note-transport-service/.claude/worktrees/rate-limit-review-fixes.Branch:
mmagician-claude/rate-limit-review-fixes, based off theev/fmthead (93ffd74).