fix(ratelimit): canonicalise client keys — IPv6 /64 + mapped-v4 unification (FIX-RLKEY) - #132
Merged
Merged
Conversation
…fied (FIX-RLKEY) The per-client budget multiplied with the client's address pool: an IPv6 client rotating temporary/privacy addresses inside its /64 got a fresh local bucket and a fresh Postgres window row per /128, and the ::ffff:a.b.c.d spellings keyed separately from the native v4 address. canon() now Unmap()s before rendering and folds IPv6 keys to the masked /64 base; the socket-peer paths canonicalise too so direct v6 clients fold identically. The one canonical string still keys the local buckets and the shared window rows end to end. The folded key remains a parseable address, so the gateway's forwarded-header rebuild (S18) now folds the runtime-bound client address at the same granularity. IPv4 keys are unchanged; IPv6 window rows under the old per-address format expire inside their minute window — no migration. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…IX-RLKEY) Advisor gate: the /64 fold applies to rate-limit keys only. The shared selection splits into two renders — ClientKey (folded key) and the new ClientAddr (unmapped but unfolded real address) — and the gateway's rewriteForwarded now derives headers from ClientAddr, so the runtime and attribution keep the exact client address while billing stays per-/64. Docs state the consequence plainly: hosts sharing one /64 share one rate-limit budget, nothing else. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
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
internal/ratelimit.canonnowUnmap()s every parseable address before rendering, so the IPv4-mapped spellings (::ffff:a.b.c.d,0:0:0:0:0:ffff:a.b.c.d) land on the single native v4 key — a dual-stack client can no longer double its budget by switching family spelling.ClientKey(folded key) and the newClientAddr(unmapped but unfolded real address). The gateway's forwarded-header rebuild (X-Forwarded-For/Forwarded/X-Real-IPtoward the runtime, S18) usesClientAddr, so the runtime still sees the client's exact address —TestProxy_IPv6ForwardsExactAddrKeyFoldspins the split.TestShared_IPv6WindowAndLocalShareKeypins that.values.yamlanddocs/runbooks/capacity.mddescribe the canonicalisation, the shared-/64 budget consequence, and the edge invariant (the right-most-untrusted XFF entry is only meaningful when every proxy hop in front is in-trusted-proxies— a pass-through trusted hop hands the key to client bytes; dual-stack edges must list their IPv6 ranges).docs/security/threat-model.mdS14 residual updated.Regression test (fails on v0.5.0, passes here)
On v0.5.0 all three fail (per-/128 keys,
::ffff:split from native v4); with this change all pass. Verified:go build ./...,gofmtclean,go vet,go test -race ./internal/ratelimit/, plus the./internal/gateway/and./internal/api/suites and a 25 sFuzzClientKeyrun.Test plan
::ffff:v4== nativev4(XFF + socket peer)Diff stat vs origin/main
Generated with Devin