Resolve real client IP behind trusted proxies (closes #94) #127

Merged
clawbot merged 10 commits from issue-94-trusted-proxies into next 2026-09-22 10:25:42 +02:00
10 Commits
Author SHA1 Message Date
sneak bdfac2b83f docs: attach parseCIDRList doc to its own function
check / check (push) Successful in 2m51s
The paragraph describing parseCIDRList sat above defaultTrustedProxies
with no separating blank line, so both bound to defaultTrustedProxies as
one doc block and parseCIDRList had none. Move it to immediately precede
func parseCIDRList.

Model: opus-4-8
2026-09-22 07:59:06 +00:00
sneak 564049db4f docs: describe the rfc1918 trusted_proxies default
check / check (push) Successful in 2m26s
Update README.md, config.example.yml and the TODO.md completed entry so
they state that an omitted trusted_proxies key defaults to the RFC 1918
private ranges, an explicitly empty list trusts no one, and an explicit
list replaces the default.

Model: opus-4-8
2026-09-22 06:58:06 +00:00
sneak e0635a7f0b feat: default trusted_proxies to the rfc1918 private ranges
An omitted trusted_proxies key now defaults to 10.0.0.0/8, 172.16.0.0/12
and 192.168.0.0/16 instead of trusting no one, matching the deployment
where pixa sits behind a proxy on a private network. An explicitly empty
list still trusts nothing, and an explicit list replaces the default;
these are distinguished by parseCIDRList returning nil only for an absent
key. Loopback is excluded, as it is not an RFC 1918 range.

Model: opus-4-8
2026-09-22 06:57:00 +00:00
sneak 9a5e7123ec test: expect rfc1918 default for omitted trusted_proxies
Per the owner ruling, an omitted trusted_proxies key now defaults to the
RFC 1918 private ranges rather than trusting no one. Update the wiring
test to expect that default, and add a case that an explicitly empty list
still trusts no one.

Model: opus-4-8
2026-09-22 06:56:25 +00:00
sneak 518e84ce67 docs: document trusted_proxies and record in TODO.md
check / check (push) Failing after 1s
Describe the trusted_proxies key in README.md and config.example.yml,
including the rightmost-non-proxy selection rule and the safe default of
trusting no one. Record the unit in TODO.md.

Model: opus-4-8
2026-09-21 23:23:56 +00:00
sneak 20a18c7345 feat: use resolved client IP in request and auth logs
Add a ClientIP middleware that resolves the client address once per
request and stores it in the context, placed ahead of logging in the
route chain. The request-logging middleware and both login-attempt logs
now read the resolved address instead of the raw peer. Behind a trusted
proxy these records show the real client; a direct, untrusted client
still shows its own address and cannot forge one. Removes the local
host:port helper now that the clientip package owns that parsing.

Model: opus-4-8
2026-09-21 23:23:56 +00:00
sneak a280fd2c34 test: cover resolved client IP in request and auth logs
Assert the ClientIP middleware stores the resolved address in the
request context for trusted and untrusted peers, that the request log
records the forwarded client rather than the proxy peer, and that a
failed-login record carries the resolved address.

Model: opus-4-8
2026-09-21 23:23:55 +00:00
sneak 4f14cd86c3 feat: add trusted-proxy-aware client IP resolver
New internal/clientip package. Resolve honors X-Forwarded-For only when
the direct peer falls inside a configured trusted-proxy range; otherwise
it returns the peer address and ignores the header, so a client
connecting directly cannot spoof its address. When the peer is trusted,
the chain is walked right to left and the first entry that is not itself
a trusted proxy is returned. Also provides request-context helpers so the
resolved address can be shared across middleware and handlers.

Tests cover the honored, ignored, and spoofed-chain cases, rightmost
selection across a mixed chain, multi-line headers, IPv6 and
IPv4-mapped peers, and the context round-trip.

Model: opus-4-8
2026-09-21 23:23:47 +00:00
sneak c5f4682b0b feat: add trusted_proxies config key
Add a trusted_proxies CIDR-list config key alongside blocked_networks.
Generalize the blocked_networks parser into parseCIDRList and
cidrListEntries, which take the key name as a parameter, so both keys
share one parser rather than a second copy. An invalid entry aborts
startup naming the key and value; an omitted or empty key leaves the
list empty.

Model: opus-4-8
2026-09-21 23:23:47 +00:00
sneak 7697822c53 test: add failing config tests for trusted_proxies
Check that a valid CIDR list lands in TrustedProxies in order, an omitted
key trusts no one, and an invalid or null value aborts startup naming the
key and value. The list parser is shared with blocked_networks, whose
tests exercise the remaining shapes.

Model: opus-4-8
2026-09-21 23:23:47 +00:00