Behind the TLS-terminating proxy pixa is deployed with, every access-log line and login record showed the proxy's address, and naively trusting X-Forwarded-For would let any client forge one.
What changed:
New trusted_proxies config key: a CIDR list parsed by the same net/netip list parser as blocked_networks (generalized to take the key name), so there is one parser, not two. An invalid entry aborts startup naming the key and value; omitted or empty trusts no one.
New internal/clientip package resolves the client address, honoring X-Forwarded-For only when the direct peer is inside a trusted range, then walking the chain right-to-left to the rightmost entry that is not itself a trusted proxy. A client connecting directly cannot influence the result.
A ClientIP middleware resolves once per request and stores the address in the context; the request-logging middleware and both login-attempt logs now read it instead of the raw peer.
Documented in README.md and config.example.yml.
Not shown by the diff: the upstream RemoteAddr fields in the fetcher and cache are the origin server's address, not a client, and were left alone.
Disclosures:
Judgement call: resolves from X-Forwarded-For only, not X-Real-IP; that header carries the verifiable chain the definition of done walks.
Verification: local docker build --target builder gate green; hosted CI is down (sneak/vaultik#133).
Model: opus-4-8
Behind the TLS-terminating proxy pixa is deployed with, every access-log line and login record showed the proxy's address, and naively trusting `X-Forwarded-For` would let any client forge one.
What changed:
- New `trusted_proxies` config key: a CIDR list parsed by the same `net/netip` list parser as `blocked_networks` (generalized to take the key name), so there is one parser, not two. An invalid entry aborts startup naming the key and value; omitted or empty trusts no one.
- New `internal/clientip` package resolves the client address, honoring `X-Forwarded-For` only when the direct peer is inside a trusted range, then walking the chain right-to-left to the rightmost entry that is not itself a trusted proxy. A client connecting directly cannot influence the result.
- A `ClientIP` middleware resolves once per request and stores the address in the context; the request-logging middleware and both login-attempt logs now read it instead of the raw peer.
- Documented in `README.md` and `config.example.yml`.
Not shown by the diff: the upstream `RemoteAddr` fields in the fetcher and cache are the origin server's address, not a client, and were left alone.
Disclosures:
- Judgement call: resolves from `X-Forwarded-For` only, not `X-Real-IP`; that header carries the verifiable chain the definition of done walks.
- Verification: local `docker build --target builder` gate green; hosted CI is down (https://git.eeqj.de/sneak/vaultik/issues/133).
Model: opus-4-8
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
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
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
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
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
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
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.
Behind the TLS-terminating proxy pixa is deployed with, every access-log line and login record showed the proxy's address, and naively trusting
X-Forwarded-Forwould let any client forge one.What changed:
trusted_proxiesconfig key: a CIDR list parsed by the samenet/netiplist parser asblocked_networks(generalized to take the key name), so there is one parser, not two. An invalid entry aborts startup naming the key and value; omitted or empty trusts no one.internal/clientippackage resolves the client address, honoringX-Forwarded-Foronly when the direct peer is inside a trusted range, then walking the chain right-to-left to the rightmost entry that is not itself a trusted proxy. A client connecting directly cannot influence the result.ClientIPmiddleware resolves once per request and stores the address in the context; the request-logging middleware and both login-attempt logs now read it instead of the raw peer.README.mdandconfig.example.yml.Not shown by the diff: the upstream
RemoteAddrfields in the fetcher and cache are the origin server's address, not a client, and were left alone.Disclosures:
X-Forwarded-Foronly, notX-Real-IP; that header carries the verifiable chain the definition of done walks.docker build --target buildergate green; hosted CI is down (sneak/vaultik#133).Model: opus-4-8
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.