Skip to content

wiki: key anonymous rate limits by the IPv6 /64 - #102

Merged
Deicyde merged 2 commits into
mainfrom
claude/limiter-ipv6-prefix
Oct 19, 2026
Merged

Deicyde merged 2 commits into
mainfrom
claude/limiter-ipv6-prefix

Conversation

@Deicyde

@Deicyde Deicyde commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

The anonymous rate limiters key the raw CF-Connecting-IP: flag: (POST /api/flag/:slug), csp: (the CSP report sink from #79), mcp: (POST /mcp) and brainapi-ip: (the Brain REST reads). An IPv6 client usually holds a whole /64, so it can rotate addresses and get a fresh budget on every request.

What changed

  • limiterIp in wiki/src/security.ts keys an IPv6 address by its /64 (2001:db8:1:2::/64, however the address is written). Everything else keys as given, as today: IPv4, the ::/64 block (loopback and the IPv4-mapped forms, so ::ffff:cb00:7107 and its neighbours don't share one bucket), and anything it can't parse.
  • All four limiters use it. Each keeps its fallback for a missing header ("" for flags and the CSP sink, "unknown" for /mcp and the REST reads).
  • The /brain/api and /mcp pages and docs/BRAIN-API.md say "per /64 for IPv6" next to "per IP"; neither page is render-cached. The env.ts and wrangler.jsonc comments match.

Tests

  • New wiki/test/limiter-ip.test.ts: the parser (IPv4 and ::/64 addresses kept whole, four spellings of one /64, a compressed form, and a missing header and malformed input passed through), plus one request from each of two addresses in the same /64 to all four routes, asserting the eight keys the limiters receive.
  • Locally, that file plus security-headers, flags, mcp and brain-api (205 tests) and tsc --noEmit pass. My local install has hono 4.12.34; CI uses the lockfile's 4.13.12.
  • Mutants caught: one call site left on the raw header, no hex normalization, a loose group count, no ::/64 rule.

Notes

  • Release-affecting (wiki/src, wiki/wrangler.jsonc). Merge it before release B is built to ship it in B, or after B is promoted; not in between.
  • Clients behind one IPv6 /64 now share a budget, as IPv4 clients behind one NAT already do.

The anonymous limiters (flag:, csp:, mcp: and brainapi-ip:) keyed the
raw CF-Connecting-IP. An IPv6 host usually holds a whole /64, so it could
rotate addresses and get a fresh budget on every request.

limiterIp in security.ts keys an IPv6 address by its /64 and leaves IPv4,
IPv4-mapped IPv6 and anything it can't parse as they were; all four
limiters use it, and each keeps its own fallback for a missing header.
The /brain/api and /mcp pages and docs/BRAIN-API.md say "per /64 for
IPv6" next to "per IP".
Loopback and the IPv4-mapped forms all sit in ::/64, so keying them by the
/64 would put every such client (::ffff:cb00:7107 and friends) in one
bucket. Key them by the full address, like IPv4.
@Deicyde
Deicyde merged commit d5c9c62 into main Oct 19, 2026
4 checks passed
@Deicyde

Deicyde commented Oct 19, 2026

Copy link
Copy Markdown
Owner Author

Merged before release B's build (merge commit d5c9c627). An independent review found no defects. limiterIp handles the IPv6 text forms CF-Connecting-IP can carry: compressed and full, leading zeros, uppercase, and :: standing for one or more groups. A second ::, nine groups, a :: standing for zero groups, stray colons, zone ids and brackets all pass through unchanged, so malformed input can never land in another client's /64. A 30-input differential check against Python's ipaddress agreed everywhere except one case: a dotted IPv4 tail outside ::/64 keys per address, which is harmless. All four IP-keyed limiters use it, every other limit() call is keyed by user id, and neither page is render-cached. Two optional follow-ups for after B is promoted: the /mcp 429 text still says "per IP", and docs/BRAIN-API.md doesn't list the REST reads' own 120/min budget.

Posted by the WikiLean PR swarm (lead).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant