Skip to content

feat(session): accept CIDR ranges in trusted_proxies - #98

Merged
allen0099 merged 1 commit into
masterfrom
feat/trusted-proxies-cidr
Sep 25, 2026
Merged

allen0099 merged 1 commit into
masterfrom
feat/trusted-proxies-cidr

Conversation

@allen0099

Copy link
Copy Markdown
Owner

Closes #73.

Changes

  • New SessionConfig.is_trusted_proxy(address):
    • An IP address matches an entry holding the same address, or a CIDR range that contains it. ipaddress.ip_network(..., strict=False) parses the entries, so host bits in an entry like 10.0.0.8/24 are ignored.
    • An IPv4-mapped IPv6 peer (::ffff:10.1.2.3, as dual-stack sockets report it) matches IPv4 entries.
    • Non-IP entries and peers, such as TestClient's testclient, keep exact string matching.
    • Parsed entries and addresses are cached with lru_cache, not stored on the model, so a config made with model_copy(update=...) (which skips validation) never matches against stale ranges.
  • get_client_ip() uses it for the trusted-peer check and for the rightmost-untrusted X-Forwarded-For walk.
  • Validation: an entry containing / that does not parse as a range (for example 10.0.0.0/33) raises at config creation.
  • Docs: the field description and docs/SESSION.md drop the "CIDR ranges are not supported" note and gain a range example. The CHANGELOG [Unreleased] section has an Added entry.

Compatibility

Every entry that matched a peer before still matches the same peer. Matching only widens for IP entries: equal addresses written differently (2001:DB8::1 / 2001:db8::1) and the IPv4-mapped form now also match.

The one new rejection is an entry containing / that is not a valid range. Such an entry could only ever have matched a peer string containing /, which no socket reports.

Tests

tests/session/test_client_ip.py covers:

  • Matches: IPv4 and IPv6 ranges, exact addresses, IPv6 case, host bits, IPv4-mapped peers, and testclient.
  • Non-matches: an address outside the range, an IPv6 peer against an IPv4 range, and IP vs. non-IP entries.
  • Malformed ranges.
  • An X-Forwarded-For walk that skips every hop inside a trusted range.
  • A model_copy update.

Mutation checks:

  • Dropping the IPv4-mapped handling fails the dual-stack case.
  • Dropping the validator fails all three malformed-range cases.

Local results:

  • ruff check/format: clean.
  • mypy (package strict, tests, scripts): clean.
  • zensical build --strict: passes.
  • Full suite with CACHEX_REQUIRE_LIVE_SERVERS=1 against Redis and Memcached: 716 passed, 100% coverage.
  • tests/session on Python 3.10: passes.

SessionConfig.is_trusted_proxy() matches an IP peer against single
addresses and CIDR ranges (IPv4-mapped IPv6 peers count as IPv4), and
keeps exact string matching for non-IP entries such as testclient. The
middleware uses it for both the peer check and the X-Forwarded-For walk.
Entries containing '/' that do not parse as a range fail validation.

Closes #73
@allen0099
allen0099 merged commit 4ecb77b into master Sep 25, 2026
10 checks passed
@allen0099
allen0099 deleted the feat/trusted-proxies-cidr branch September 25, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support CIDR ranges in SessionConfig.trusted_proxies

1 participant