2.6 KiB
plan, phase, status, completed_at, commits, self_check
| plan | phase | status | completed_at | commits | self_check | |
|---|---|---|---|---|---|---|
| 06-05 | 06-performance-production-hardening | complete | 2026-06-04 |
|
PASSED |
What was done
D-11 — Trusted-proxy CIDR check in get_client_ip
Before: get_client_ip unconditionally returned the first value of
X-Forwarded-For if present, falling back to request.client.host. Any
external caller could spoof their IP by adding an X-Forwarded-For header.
After: get_client_ip only honours X-Forwarded-For when the direct peer
(request.client.host) is in one of the trusted proxy CIDRs:
127.0.0.0/8(loopback)172.16.0.0/12(Docker / internal)192.168.0.0/16(LAN)::1/128(IPv6 loopback)
Untrusted peers always return their own address. The CIDR list is a module-level
constant _TRUSTED_PROXY_NETS in backend/deps/utils.py. No new public function
was added; only the body and docstring of get_client_ip changed.
D-12 — Per-account rate limiter (account_limiter)
Created backend/services/rate_limiting.py (new file, 18 lines):
_account_key(request): readsrequest.state.current_user.idif set, falls back torequest.client.host, then"anonymous".account_limiter = Limiter(key_func=_account_key): singleton exported for use in routers.
Wiring
backend/api/auth.py: removedfrom slowapi.util import get_remote_address; changedlimiter = Limiter(key_func=get_remote_address)tolimiter = Limiter(key_func=get_client_ip).backend/main.py: addedfrom services.rate_limiting import account_limiter.backend/api/documents.py: 9 endpoints decorated with@account_limiter.limit("100/minute"); each handler's first line setsrequest.state.current_user = current_user(A1 ordering invariant).backend/api/cloud.py: 7 endpoints decorated identically (same pattern).
Decorator counts:
documents.py: 9@account_limiter.limitdecorators, 9 assignmentscloud.py: 7@account_limiter.limitdecorators, 7 assignments
D-13 — 8 xfail tests promoted
All 8 stubs in backend/tests/test_rate_limiting.py promoted from xfail to
real assertions. No @pytest.mark.xfail markers remain.
A cross-test contamination issue was also fixed: backend/tests/conftest.py
gained an autouse fixture reset_rate_limiter that calls
account_limiter._storage.reset() before and after each test, preventing
in-memory counters from accumulating across tests and causing spurious 429s.
Final pytest result
352 passed, 5 skipped, 7 xfailed
1 pre-existing failure (test_extract_docx — ModuleNotFoundError: No module named 'docx', unrelated to this plan)
All 8 rate limiting tests pass (8 passed, 0 xfailed).