security: document rate limiter IP-keying, anti-spoofing verification, shared-bucket tradeoff (#144)

fen
2026-09-10 11:12:34 -05:00
parent 0440d32742
commit 5262033ec5
+21
@@ -0,0 +1,21 @@
# Security Notes
Notes from the exploratory security pass on palette-dev (2026-09-09). No issues remain open from these findings; this page records the verified behavior and known tradeoffs.
## Rate limiting
### Bucket key and header spoofing (verified, #144)
The create rate limiter is keyed on `clientIP`, which takes the **rightmost** entry in `X-Forwarded-For` (the address of the immediate peer as seen by the trusted proxy layer). This means:
- Spoofed `X-Forwarded-For`, `X-Real-Ip`, and `True-Client-Ip` headers **cannot bypass or poison the bucket**.
- Verified on palette-dev: six rapid `POST /api/pastes` from one IP gave 201 x5 then 429; repeating while sending `X-Forwarded-For: 1.2.3.4`, `X-Real-Ip: 9.9.9.9`, and `True-Client-Ip: 3.3.3.3` still returned 429 for every request (no bypass).
### Create budget
- Budget per IP: **burst of 5** creates, refilled at **1 request/second**.
- Sixth rapid create from the same IP gets `429 Too Many Requests`.
### Known inherent limitation: shared egress IP
The limiter is keyed solely by IP, so clients behind a single egress address — an office NAT, a VPN exit node, a CGNAT carrier — share **one** create bucket. One heavy user can consume the burst-5 budget and cause `429` responses for everyone behind that IP. This is an inherent tradeoff of IP-keyed limiting, accepted for now; alternatives (auth-scoped or cookie-scoped buckets) would be a design change and have not been scheduled.