fix: validate the trusted X-Forwarded-For entry as client_ip

MA
9h ago
+86-2

Closes #7854

With trusted_proxy_hops set, the selected X-Forwarded-For entry became router.session.client_ip as is, so 203.0.113.7:51234, [2001:db8::1]:443 or text that isn't an address at all ended up there.

  • New _forwarded_client_ip() drops a numeric :port suffix or IPv6 brackets and returns str(ipaddress.ip_address(...)), or None if the entry isn't an address. Malformed forms (an unclosed bracket, text after ], a non-numeric or empty port) and IPv6 zone indexes (fe80::1%eth0, which ipaddress keeps without validating) are rejected rather than trimmed. It's only used at the client_ip site; _trusted_forwarded_value() and the X-Forwarded-Host / X-Forwarded-Proto handling are unchanged.
  • An invalid entry falls back to the connection's peer address and is logged at debug level (through the existing _clip), since the value is client-controlled when a deployment is misconfigured.
  • Valid IPv6 entries are now canonical (2001:DB8:0:0:0:0:0:1 becomes 2001:db8::1), and leading-zero IPv4 such as 010.0.0.1 falls back to the peer address, which ipaddress rejects.
  • The trusted_proxy_hops docs mention the normalisation, and the bugfix news fragment notes the change so other client_ip consumers can follow.

Tests: new cases in test_static_router_data_resolves_client_ip_from_trusted_proxy_hops (port, bracketed IPv6 with and without port, expanded IPv6, a non-address entry, malformed brackets and ports, zone indexes) and test_static_router_data_logs_ignored_forwarded_client_ip; 13 of them fail on main. pytest tests/units/test_app.py tests/units/test_config.py: 500 passed; ruff check/format clean.

All Submissions:

  • Have you followed the guidelines stated in CONTRIBUTING.md file?
  • Have you checked to ensure there aren't any other open Pull Requests for the desired changed?

Type of change

  • Bug fix (non-breaking change which fixes an issue)