"""The proxy trust boundary. The whole security property is "a caller cannot forge their own address", and it rests on counting in from the RIGHT of the header rather than the left. These are the cases that tell the two apart — pure functions, no request context, no database. """ from inkwell.proxy import forwarded_for, trusted_entry from inkwell.settings import live PEER = "10.0.0.1" # the socket address: our own proxy, or the caller when unproxied def test_default_is_one_hop(): # One reverse proxy terminating TLS, the only shape safe to assume. Read through # live(), which proxy.py calls and which is seeded from the defaults at import, so # a boot that never reached the database still counts one hop. assert live("trusted_proxy_hops") == 1 def test_no_proxy_ignores_the_header_entirely(): # hops=0 says nothing in front of us appends anything, so the header can only be # something a caller invented. assert forwarded_for("1.2.3.4", PEER, 0) == PEER def test_one_hop_reads_what_our_proxy_wrote(): assert forwarded_for("203.0.113.7", PEER, 1) == "203.0.113.7" def test_a_forged_prefix_is_never_selected(): # A caller sends `X-Forwarded-For: 1.2.3.4`; our proxy appends what it saw. # Reading from the left would give the caller a fresh bucket per invented value. assert forwarded_for("1.2.3.4, 203.0.113.7", PEER, 1) == "203.0.113.7" # …and padding it doesn't help either. assert forwarded_for("a, b, c, d, 203.0.113.7", PEER, 1) == "203.0.113.7" def test_two_hops_sees_past_a_cdn(): # Cloudflare appended the real client; our proxy appended Cloudflare. assert forwarded_for("203.0.113.7, 172.16.0.5", PEER, 2) == "203.0.113.7" assert forwarded_for("1.2.3.4, 203.0.113.7, 172.16.0.5", PEER, 2) == "203.0.113.7" def test_a_short_header_is_read_from_its_leftmost_entry(): # Fewer proxies than configured — a LAN visitor straight to our proxy, behind a # count set for the CDN road. The leftmost entry is what the first proxy wrote; a # caller's own entries could only have made the header longer. Falling back to the # socket address here would file every LAN visitor under the proxy's address. assert forwarded_for("192.168.0.20", PEER, 2) == "192.168.0.20" assert forwarded_for("", PEER, 1) == PEER def test_a_single_https_from_the_cdn_counts_at_two_hops(): # Cloudflare sends one `X-Forwarded-Proto: https` and our proxy passes it on # without appending, so at hops 2 it is a one-entry header — and still HTTPS. assert trusted_entry("https", 2) == "https" def test_malformed_headers_do_not_crash_or_leak_empties(): assert forwarded_for(",,,", PEER, 1) == PEER assert forwarded_for(" , 203.0.113.7 , ", PEER, 1) == "203.0.113.7" def test_the_key_is_length_bounded(): # It becomes a dict key in the limiter; an unbounded header must not become an # unbounded allocation. assert len(forwarded_for("x" * 5000, PEER, 1)) <= 64 def test_trusted_entry_reports_absence_rather_than_guessing(): # `is_https` needs to tell "no trusted entry" apart from "an entry saying http", # which is why this returns None rather than a default. assert trusted_entry("", 1) is None assert trusted_entry("https", 0) is None assert trusted_entry("http, https", 1) == "https"