This was generated by AI — surfaced during the #194 swarm review (follow-up to #190).
Observation
rest/clientip.go keeps the parsed trusted-proxy set in a package-level var trustedProxies []*net.IPNet, written once by InitTrustedProxies() at startup and read thereafter by clientIP. The "write once before any listener serves, read-only after" invariant is enforced only by call ordering + a comment, not by the type — any code in package rest could reassign it, and the read/write are unsynchronized.
Why it's benign today
- The composition root wires
InitTrustedProxies() before serving (and servers/server_test.go has a source-level guard pinning that ordering and the fatal-on-malformed behavior).
- A read before init yields
nil → trust-nothing → the safe direction.
- Boot is single-threaded, so there is no real race in the current lifecycle.
Possible direction (for triage — low priority)
Per the type-design review, wrap the cache in atomic.Pointer[[]*net.IPNet] behind an unexported accessor (~5 lines), turning the once-write/read-after lifecycle from a documented convention into a structural one and removing the theoretical race. Only pays off if a concurrent writer ever appears on this surface (e.g. the deferred per-key rate-limiting work in PRD #112).
Design context: ADR 0005 (docs/adr/0005-trusted-proxy-client-ip.md). Surfaced by the type-design review on PR #194. needs-triage.
Observation
rest/clientip.gokeeps the parsed trusted-proxy set in a package-levelvar trustedProxies []*net.IPNet, written once byInitTrustedProxies()at startup and read thereafter byclientIP. The "write once before any listener serves, read-only after" invariant is enforced only by call ordering + a comment, not by the type — any code inpackage restcould reassign it, and the read/write are unsynchronized.Why it's benign today
InitTrustedProxies()before serving (andservers/server_test.gohas a source-level guard pinning that ordering and the fatal-on-malformed behavior).nil→ trust-nothing → the safe direction.Possible direction (for triage — low priority)
Per the type-design review, wrap the cache in
atomic.Pointer[[]*net.IPNet]behind an unexported accessor (~5 lines), turning the once-write/read-after lifecycle from a documented convention into a structural one and removing the theoretical race. Only pays off if a concurrent writer ever appears on this surface (e.g. the deferred per-key rate-limiting work in PRD #112).Design context: ADR 0005 (
docs/adr/0005-trusted-proxy-client-ip.md). Surfaced by the type-design review on PR #194.needs-triage.