realIP in internal/middleware/middleware.go reads X-Forwarded-For only when the direct peer is a trusted proxy, but then takes the first entry. A proxy that appends to an X-Forwarded-For the client sent leaves the client's own value first, so the client chooses the address dnswatcher logs and, once #180 lands, the address its /metrics rate limit counts against: a client can escape the limit by sending a different value each time.
Definition of done
From X-Forwarded-For, the client address is the rightmost entry that is not a trusted proxy, walking from the right, using the existing trusted-proxy check (no second list). If every entry is a trusted proxy, the leftmost is used.
X-Real-IP handling is unchanged.
Tests: a client-sent first entry followed by the real client address added by a trusted proxy gives the real client address; a chain of several trusted proxies; the untrusted-peer case still ignores both headers. The tests fail on the old code.
Sequencing: after #180, which uses realIP for the rate limit and changes the same file.
Model: opus-5-5
Found while implementing https://git.eeqj.de/sneak/dnswatcher/issues/101 (https://git.eeqj.de/sneak/dnswatcher/pulls/180).
`realIP` in `internal/middleware/middleware.go` reads `X-Forwarded-For` only when the direct peer is a trusted proxy, but then takes the first entry. A proxy that appends to an `X-Forwarded-For` the client sent leaves the client's own value first, so the client chooses the address dnswatcher logs and, once https://git.eeqj.de/sneak/dnswatcher/pulls/180 lands, the address its `/metrics` rate limit counts against: a client can escape the limit by sending a different value each time.
## Definition of done
- From `X-Forwarded-For`, the client address is the rightmost entry that is not a trusted proxy, walking from the right, using the existing trusted-proxy check (no second list). If every entry is a trusted proxy, the leftmost is used.
- `X-Real-IP` handling is unchanged.
- Tests: a client-sent first entry followed by the real client address added by a trusted proxy gives the real client address; a chain of several trusted proxies; the untrusted-peer case still ignores both headers. The tests fail on the old code.
- Sequencing: after https://git.eeqj.de/sneak/dnswatcher/pulls/180, which uses `realIP` for the rate limit and changes the same file.
Model: opus-5-5
clawbot
added this to the 1.0 milestone 2026-10-01 21:43:12 +02:00
Implemented in #183: the client address is now the rightmost X-Forwarded-For entry that is not a trusted proxy, with all header lines read as one list; X-Real-IP is unchanged.
Model: opus-5-5
Implemented in https://git.eeqj.de/sneak/dnswatcher/pulls/183: the client address is now the rightmost `X-Forwarded-For` entry that is not a trusted proxy, with all header lines read as one list; `X-Real-IP` is unchanged.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found while implementing #101 (#180).
realIPininternal/middleware/middleware.goreadsX-Forwarded-Foronly when the direct peer is a trusted proxy, but then takes the first entry. A proxy that appends to anX-Forwarded-Forthe client sent leaves the client's own value first, so the client chooses the address dnswatcher logs and, once #180 lands, the address its/metricsrate limit counts against: a client can escape the limit by sending a different value each time.Definition of done
X-Forwarded-For, the client address is the rightmost entry that is not a trusted proxy, walking from the right, using the existing trusted-proxy check (no second list). If every entry is a trusted proxy, the leftmost is used.X-Real-IPhandling is unchanged.realIPfor the rate limit and changes the same file.Model: opus-5-5
Implemented in #183: the client address is now the rightmost
X-Forwarded-Forentry that is not a trusted proxy, with all header lines read as one list;X-Real-IPis unchanged.Model: opus-5-5