From e0a95193d82a5c6f9ce5c2909345e7e39fea119d Mon Sep 17 00:00:00 2001 From: Nathan Esquenazi Date: Fri, 10 Apr 2026 01:24:18 -0700 Subject: [PATCH] fix: CSRF check supports reverse proxy headers (#218) (#219) Tests pass: 20/21 QA suite (1 known skip), all browser API sanity checks green, CSRF fix verified end-to-end. --- api/routes.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/api/routes.py b/api/routes.py index da841c7..0ce57a8 100644 --- a/api/routes.py +++ b/api/routes.py @@ -32,12 +32,21 @@ def _check_csrf(handler) -> bool: if not origin and not referer: return True # non-browser clients (curl, agent) have no Origin target = origin or referer - # Allow same-origin: Origin must match Host - if host and target: - # Extract host:port from origin/referer - m = _re.match(r'^https?://([^/]+)', target) - if m and m.group(1) == host: - return True + # Extract host:port from origin/referer + m = _re.match(r'^https?://([^/]+)', target) + if not m: + return False + origin_host = m.group(1) + # Allow same-origin: check Host, X-Forwarded-Host (reverse proxy), and + # X-Real-Host against the origin. Reverse proxies (Caddy, nginx) set + # X-Forwarded-Host to the client's original Host header. + allowed_hosts = {h.strip() for h in [ + host, + handler.headers.get('X-Forwarded-Host', ''), + handler.headers.get('X-Real-Host', ''), + ] if h.strip()} + if origin_host in allowed_hosts: + return True return False from api.models import ( Session, get_session, new_session, all_sessions, title_from,