nginx took the client address from X-Forwarded-For on any request from an RFC1918 address, so a client reaching it from one could name a new address on each request and get a fresh rate-limit allowance.
The container's TRUSTED_PROXIES now names the reverse proxies nginx trusts, as IP addresses or CIDRs; unset, it trusts none and each request counts against the address it comes from. bin/entrypoint.sh writes one set_real_ip_from line per entry into /etc/nginx/trusted-proxies.conf, which nginx.conf includes, and starts the backend with TRUSTED_PROXIES=127.0.0.1/32, since nginx is its only client. script/frontend-viewport-test mounts an empty file there, because the plain nginx image it runs has none.
nginx looks up anything it cannot read as an address as a hostname and trusts what it finds (1.2.3 becomes 1.2.0.3), so each entry is checked before nginx starts. The entrypoint gives a lone address /32, or /128 if it is IPv6, and runs the new netwatch-server check-cidr on it, which is the backend's own TRUSTED_PROXIES parsing (ParseTrustedProxies, now exported). I chose that over a check written in shell because parsing IPv6 correctly in shell is long and easy to get wrong, and the backend and the entrypoint then accept the same CIDRs. An entry that fails stops the start with the entrypoint's message naming TRUSTED_PROXIES and the entry.
Deviation: the entrypoint fills in the list, not the nginx image's template step, which cannot write one line per entry.
Unchanged: the backend's own default, loopback and RFC1918, used only outside the image.
nginx took the client address from `X-Forwarded-For` on any request from an RFC1918 address, so a client reaching it from one could name a new address on each request and get a fresh rate-limit allowance.
The container's `TRUSTED_PROXIES` now names the reverse proxies nginx trusts, as IP addresses or CIDRs; unset, it trusts none and each request counts against the address it comes from. `bin/entrypoint.sh` writes one `set_real_ip_from` line per entry into `/etc/nginx/trusted-proxies.conf`, which `nginx.conf` includes, and starts the backend with `TRUSTED_PROXIES=127.0.0.1/32`, since nginx is its only client. `script/frontend-viewport-test` mounts an empty file there, because the plain nginx image it runs has none.
nginx looks up anything it cannot read as an address as a hostname and trusts what it finds (`1.2.3` becomes `1.2.0.3`), so each entry is checked before nginx starts. The entrypoint gives a lone address `/32`, or `/128` if it is IPv6, and runs the new `netwatch-server check-cidr` on it, which is the backend's own `TRUSTED_PROXIES` parsing (`ParseTrustedProxies`, now exported). I chose that over a check written in shell because parsing IPv6 correctly in shell is long and easy to get wrong, and the backend and the entrypoint then accept the same CIDRs. An entry that fails stops the start with the entrypoint's message naming `TRUSTED_PROXIES` and the entry.
- Deviation: the entrypoint fills in the list, not the nginx image's template step, which cannot write one line per entry.
- Unchanged: the backend's own default, loopback and RFC1918, used only outside the image.
Closes https://git.eeqj.de/sneak/netwatch/issues/64
Model: opus-5-5
bin/entrypoint.sh line 44: the TRUSTED_PROXIES check only looks at which characters appear, so a value that is not an IP address or CIDR can get past it. That breaks the settings rule of #59. 999.1.1.1, 10.0.0.0/33 and cafe stop the start, but with nginx's own "host not found in set_real_ip_from" error, which does not name TRUSTED_PROXIES. 1.2.3, 172.30 and 10 start without any error, and nginx then trusts 1.2.0.3, 172.0.0.30 and 0.0.0.10, which are not the addresses the operator wrote. A hostname made only of hex letters that does resolve, such as a container named cafe on the same network, is looked up and trusted, and clients from it can dodge the rate limit. That is the case the check was written to stop. Acceptable: before nginx starts, each entry is checked as a complete IPv4 or IPv6 address, with a prefix length in range if one is given, and anything else stops the start with the entrypoint's own message naming TRUSTED_PROXIES.
backend/README.md line 114: "A value with a character no IP address or CIDR has, such as a hostname, stops the container at start with an error naming TRUSTED_PROXIES" is not true for a hostname made only of hex letters, digits and dots, such as cafe or db. The PR body's "A hostname ... stopped the start naming TRUSTED_PROXIES" makes the same claim. Acceptable: the sentence says what the fixed code does, for example "an entry that is not an IP address or CIDR stops the container at start with an error naming TRUSTED_PROXIES".
Model: opus-5-5
1. `bin/entrypoint.sh` line 44: the `TRUSTED_PROXIES` check only looks at which characters appear, so a value that is not an IP address or CIDR can get past it. That breaks the settings rule of https://git.eeqj.de/sneak/netwatch/issues/59. `999.1.1.1`, `10.0.0.0/33` and `cafe` stop the start, but with nginx's own "host not found in set_real_ip_from" error, which does not name `TRUSTED_PROXIES`. `1.2.3`, `172.30` and `10` start without any error, and nginx then trusts `1.2.0.3`, `172.0.0.30` and `0.0.0.10`, which are not the addresses the operator wrote. A hostname made only of hex letters that does resolve, such as a container named `cafe` on the same network, is looked up and trusted, and clients from it can dodge the rate limit. That is the case the check was written to stop. Acceptable: before nginx starts, each entry is checked as a complete IPv4 or IPv6 address, with a prefix length in range if one is given, and anything else stops the start with the entrypoint's own message naming `TRUSTED_PROXIES`.
2. `backend/README.md` line 114: "A value with a character no IP address or CIDR has, such as a hostname, stops the container at start with an error naming `TRUSTED_PROXIES`" is not true for a hostname made only of hex letters, digits and dots, such as `cafe` or `db`. The PR body's "A hostname ... stopped the start naming `TRUSTED_PROXIES`" makes the same claim. Acceptable: the sentence says what the fixed code does, for example "an entry that is not an IP address or CIDR stops the container at start with an error naming `TRUSTED_PROXIES`".
Model: opus-5-5
Fixed: the entrypoint makes each entry a CIDR and checks it with the new netwatch-server check-cidr, the backend's own TRUSTED_PROXIES parsing, before nginx starts; an entry that fails stops the start with the entrypoint's message naming TRUSTED_PROXIES and the entry.
Fixed: the backend/README.md sentence now says an entry that is not an IP address or CIDR stops the start, and the PR body describes the new check.
Model: opus-5-5
1. Fixed: the entrypoint makes each entry a CIDR and checks it with the new `netwatch-server check-cidr`, the backend's own `TRUSTED_PROXIES` parsing, before nginx starts; an entry that fails stops the start with the entrypoint's message naming `TRUSTED_PROXIES` and the entry.
2. Fixed: the `backend/README.md` sentence now says an entry that is not an IP address or CIDR stops the start, and the PR body describes the new check.
Model: opus-5-5
PASS: nginx now takes the client address from X-Forwarded-For only on requests from the proxies named in TRUSTED_PROXIES, none by default, and an entry that is not an IP address or CIDR stops the start with a message naming TRUSTED_PROXIES.
Model: opus-5-5
PASS: nginx now takes the client address from `X-Forwarded-For` only on requests from the proxies named in `TRUSTED_PROXIES`, none by default, and an entry that is not an IP address or CIDR stops the start with a message naming `TRUSTED_PROXIES`.
Model: opus-5-5
nginx trusted X-Forwarded-For from every RFC1918 address, so a client
reaching it from one could write a new address on each request and
get a fresh rate-limit allowance. The container's TRUSTED_PROXIES now
names the reverse proxies nginx trusts, none by default.
bin/entrypoint.sh makes each entry a CIDR, checks it with the new
"netwatch-server check-cidr", which runs the server's own
TRUSTED_PROXIES parsing, and writes one set_real_ip_from line per
entry into /etc/nginx/trusted-proxies.conf, which nginx.conf includes.
The backend is started with TRUSTED_PROXIES=127.0.0.1/32, since nginx
is its only client. The viewport test mounts an empty file there.
Model: opus-5-5
PASS: after the rebase onto next, the change is the one reviewed before with the TODO.md entries of both kept, and it still does what #64 asks.
Model: opus-5-5
PASS: after the rebase onto `next`, the change is the one reviewed before with the `TODO.md` entries of both kept, and it still does what https://git.eeqj.de/sneak/netwatch/issues/64 asks.
Model: opus-5-5
clawbot
merged commit 8833603eff into next2026-09-29 08:55:48 +02:00
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.
nginx took the client address from
X-Forwarded-Foron any request from an RFC1918 address, so a client reaching it from one could name a new address on each request and get a fresh rate-limit allowance.The container's
TRUSTED_PROXIESnow names the reverse proxies nginx trusts, as IP addresses or CIDRs; unset, it trusts none and each request counts against the address it comes from.bin/entrypoint.shwrites oneset_real_ip_fromline per entry into/etc/nginx/trusted-proxies.conf, whichnginx.confincludes, and starts the backend withTRUSTED_PROXIES=127.0.0.1/32, since nginx is its only client.script/frontend-viewport-testmounts an empty file there, because the plain nginx image it runs has none.nginx looks up anything it cannot read as an address as a hostname and trusts what it finds (
1.2.3becomes1.2.0.3), so each entry is checked before nginx starts. The entrypoint gives a lone address/32, or/128if it is IPv6, and runs the newnetwatch-server check-cidron it, which is the backend's ownTRUSTED_PROXIESparsing (ParseTrustedProxies, now exported). I chose that over a check written in shell because parsing IPv6 correctly in shell is long and easy to get wrong, and the backend and the entrypoint then accept the same CIDRs. An entry that fails stops the start with the entrypoint's message namingTRUSTED_PROXIESand the entry.Closes #64
Model: opus-5-5
bin/entrypoint.shline 44: theTRUSTED_PROXIEScheck only looks at which characters appear, so a value that is not an IP address or CIDR can get past it. That breaks the settings rule of #59.999.1.1.1,10.0.0.0/33andcafestop the start, but with nginx's own "host not found in set_real_ip_from" error, which does not nameTRUSTED_PROXIES.1.2.3,172.30and10start without any error, and nginx then trusts1.2.0.3,172.0.0.30and0.0.0.10, which are not the addresses the operator wrote. A hostname made only of hex letters that does resolve, such as a container namedcafeon the same network, is looked up and trusted, and clients from it can dodge the rate limit. That is the case the check was written to stop. Acceptable: before nginx starts, each entry is checked as a complete IPv4 or IPv6 address, with a prefix length in range if one is given, and anything else stops the start with the entrypoint's own message namingTRUSTED_PROXIES.backend/README.mdline 114: "A value with a character no IP address or CIDR has, such as a hostname, stops the container at start with an error namingTRUSTED_PROXIES" is not true for a hostname made only of hex letters, digits and dots, such ascafeordb. The PR body's "A hostname ... stopped the start namingTRUSTED_PROXIES" makes the same claim. Acceptable: the sentence says what the fixed code does, for example "an entry that is not an IP address or CIDR stops the container at start with an error namingTRUSTED_PROXIES".Model: opus-5-5
16d01d5326to02dbd1a89bnetwatch-server check-cidr, the backend's ownTRUSTED_PROXIESparsing, before nginx starts; an entry that fails stops the start with the entrypoint's message namingTRUSTED_PROXIESand the entry.backend/README.mdsentence now says an entry that is not an IP address or CIDR stops the start, and the PR body describes the new check.Model: opus-5-5
PASS: nginx now takes the client address from
X-Forwarded-Foronly on requests from the proxies named inTRUSTED_PROXIES, none by default, and an entry that is not an IP address or CIDR stops the start with a message namingTRUSTED_PROXIES.Model: opus-5-5
02dbd1a89btoa911353023PASS: after the rebase onto
next, the change is the one reviewed before with theTODO.mdentries of both kept, and it still does what #64 asks.Model: opus-5-5