Honour forwarded headers only from trusted proxies (closes #259)
check / check (push) Failing after 6s

A new ClientIP middleware sets request.RemoteAddr to the client's
address before the request is logged. X-Forwarded-For and X-Real-IP
count only when the peer is in TRUSTED_PROXIES, which defaults to the
RFC 1918 ranges. The login rate limit, the session's IP address and
hostname, and the request log all use that address.

Model: opus-5-5
This commit is contained in:
sneak committed 2026-11-10 19:57:27 +00:00
1 parent bc8aa6bb96
commit 51d9d774fd
10 files changed
+519 -42

No files matched your search

+44 -18
View File
@@ -1339,8 +1339,9 @@ Every session has a hostmask in IRC's form, `nick!username@hostname`:
from, or that IP address when the lookup gives no name. The lookup waits up to
three seconds.
Over the HTTP API, the server takes the client's IP address from the first entry
of the `X-Forwarded-For` header, then `X-Real-IP`, then the TCP connection (see
Over the HTTP API, the client's IP address is the address of the TCP connection.
When that connection comes from a trusted proxy, the server reads the address
from `X-Forwarded-For` or `X-Real-IP` instead (see
[Reverse proxy](#reverse-proxy)).
Every user can see a hostmask: in `RPL_WHOISUSER` (`311`), `RPL_WHOREPLY`
@@ -1450,10 +1451,9 @@ over the limit gets `429` `too many login attempts, try again later` with
attempt is more than 15 minutes old. Session creation has no rate limit; the
hashcash stamp is its cost.
The IP address comes from the first entry of `X-Forwarded-For`, else from
`X-Real-IP`, else from the TCP connection. Without a reverse proxy that
overwrites those headers, a client can set them itself and so pick any address
(see [Reverse proxy](#reverse-proxy)).
The IP address is the client's address as described in
[Reverse proxy](#reverse-proxy). A client outside the trusted proxies cannot
change it with `X-Forwarded-For` or `X-Real-IP`.
### Writing a client
@@ -1714,10 +1714,14 @@ Every setting is an environment variable. The server also reads a `.env` file in
its working directory, and a YAML file `neoirc.yaml` in `/etc/neoirc/` or
`$HOME/.config/neoirc/` with the same names as keys. A variable from the
environment wins over one in `.env`, and both win over `neoirc.yaml`. An
environment variable set to the empty string counts as unset, except
`IRC_LISTEN_ADDR`, where the empty string turns the IRC listener off. A
`neoirc.yaml` that is not valid YAML stops the server at startup. A setting that
does not parse or is out of range also stops the server at startup, and the
environment variable set to the empty string counts as unset, with two
exceptions:
- For `IRC_LISTEN_ADDR`, the empty string turns the IRC listener off.
- For `TRUSTED_PROXIES`, the empty string trusts no proxy.
A `neoirc.yaml` that is not valid YAML stops the server at startup. A setting
that does not parse or is out of range also stops the server at startup, and the
error names the setting.
- `PORT` (default `8080`): the HTTP port. `0` lets the system pick a free port.
@@ -1747,6 +1751,11 @@ error names the setting.
address. A fraction such as `0.5` is allowed. It must be above `0`.
- `LOGIN_RATE_BURST` (default `5`): how many attempts an address may make at
once before the rate applies. It must be at least `1`.
- `TRUSTED_PROXIES` (default `10.0.0.0/8,172.16.0.0/12,192.168.0.0/16`): the
reverse proxies whose `X-Forwarded-For` and `X-Real-IP` headers the server
believes. It is a comma-separated list of networks, such as `10.0.0.0/8`, and
single addresses, such as `192.0.2.1`. A list replaces the default entirely.
The empty string trusts nothing. See [Reverse proxy](#reverse-proxy).
- `SESSION_IDLE_TIMEOUT` (default `720h`, 30 days): a duration with a unit, `h`
for hours, `m` for minutes or `s` for seconds, such as `720h` or `36h30m`. A
client with no request for this long is removed, and a session left with no
@@ -1804,14 +1813,30 @@ Run the server behind a reverse proxy that terminates TLS. The server itself
speaks plain HTTP, and the auth cookie is `Secure`, so browsers send it only
over HTTPS. The proxy must:
- Set `X-Forwarded-For` to the client's address, replacing any value the client
sent. The server reads that header first and trusts it from anyone; it reads
`X-Real-IP` only when `X-Forwarded-For` is absent. Without a proxy that
overwrites the header, a client can choose its own address, which sets its
hostname and lets it dodge the login rate limit.
- Connect from an address in `TRUSTED_PROXIES`. The default trusts the private
ranges `10.0.0.0/8`, `172.16.0.0/12` and `192.168.0.0/16`. A proxy on the same
host connects from `127.0.0.1`, which the default does not trust, so set
`TRUSTED_PROXIES=127.0.0.1` for it.
- Add the client's address to `X-Forwarded-For`, or set `X-Real-IP`.
- Wait at least 60 seconds for a response. A long poll can take 30 seconds, and
the server's own write timeout is 60 seconds.
The server finds the client's address in these steps:
- A connection from outside `TRUSTED_PROXIES` is the client. The server ignores
its `X-Forwarded-For` and `X-Real-IP` headers.
- For a connection from a trusted proxy, the server reads `X-Forwarded-For` from
right to left. It skips trusted addresses and takes the first untrusted one.
When every entry is trusted, it takes the leftmost.
- Without `X-Forwarded-For`, the server takes `X-Real-IP`.
- When an entry it reads is not an IP address, the server uses the proxy's
address.
The client's address sets the session's hostname and counts toward the login
rate limit. Any connection from a trusted address can choose its client address
through `X-Forwarded-For`. If clients connect directly from private addresses,
set `TRUSTED_PROXIES` to the proxy's address alone.
Caddy:
```
@@ -1861,9 +1886,10 @@ The IRC listener has no TLS.
The server logs with Go's `slog`, one line per event, to standard output. The
lines are JSON, or plain text when standard output is a terminal. Every HTTP
request is logged with its method, URL, protocol, status, latency, remote
address, user agent, referrer and request ID. `DEBUG=true` adds debug-level
lines.
request is logged with its method, URL, protocol, status, latency, client
address, user agent, referrer and request ID. The client address is the one
found as described in [Reverse proxy](#reverse-proxy). `DEBUG=true` adds
debug-level lines.
### The database