Files
inkwell/tests/test_proxy.py
T
bvandeusenandClaude Opus 5.5 a51a563fe7
CI & Build / Python lint (push) Successful in 2s
CI & Build / Build now, or wait for Android? (push) Successful in 2s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Android / Core and FFI clippy and tests (push) Skipped
Android / Kotlin + Rust (APK) (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 2s
CI & Build / Web typecheck and unit tests (push) Successful in 9s
CI & Build / Python tests (push) Successful in 13s
Android / Build the server image (push) Skipped
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / integration (push) Successful in 1m30s
CI & Build / Build & push image (push) Successful in 32s
Server comments describe the code to a stranger (#6221)
Rule 32 "Comments describe the product to a stranger" sweep over
src/, alembic/ and tests/. Comments and docstrings now say what the code
does and the reason as a present-tense constraint, briefly.

Removed:
- issue, task, note, rule, milestone and family-idea references
- "used to" / before-and-after narration
- quotes of decisions and incident stories from the deploying instance
- paragraph-length arguments, where a line or two carries the fact

Kept every hazard as a present-tense fact, e.g. timing oracles, the
proxy hop direction, and migrations that must stay frozen.

Also:
- Three test names lose "still"/"used to"/"pre_m304" phrasing.
- pyproject's description drops "FabledSword family".

The history these comments carried stays in Scribe and in git log.
A comment-stripping diff (Scribe snippet #6226 "codeonly.py — prove a
diff changed only comments") shows no code changed apart from the
renamed tests and the description string.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:07:47 -04:00

68 lines
2.8 KiB
Python

"""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_falls_back_rather_than_reaching_left():
# Fewer proxies than configured: the socket address, rather than an entry no
# proxy of ours wrote.
assert forwarded_for("203.0.113.7", PEER, 2) == PEER
assert forwarded_for("", PEER, 1) == PEER
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"