From 767886ac0646564f28e1c95ecb5823bdfb1fc78a Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:55:21 -0400 Subject: [PATCH] fix(web): don't echo the refused Origin/Referer back in the 403 body The claimed origin is attacker-chosen, so the refusal reason in the response names only which header failed; the values are logged instead. Clears Codacy's directly-returned-format-string finding. Co-Authored-By: Claude Opus 5.5 --- test/test_web_origin_guard.py | 4 +++- web_interface/origin_guard.py | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/test/test_web_origin_guard.py b/test/test_web_origin_guard.py index 8565918b..b7b04601 100644 --- a/test/test_web_origin_guard.py +++ b/test/test_web_origin_guard.py @@ -51,7 +51,9 @@ def test_a_cross_site_origin_is_refused_for_every_changing_method(probe, method) body = resp.get_json() assert body['status'] == 'error' assert body['error_code'] == 'CROSS_SITE_REQUEST' - assert 'evil.example' in body['details'] + assert body['details'].startswith('Origin ') + # The attacker-chosen origin is logged, never echoed back in the body. + assert 'evil.example' not in resp.get_data(as_text=True) @pytest.mark.parametrize('origin', [ diff --git a/web_interface/origin_guard.py b/web_interface/origin_guard.py index efa22299..6d0c8145 100644 --- a/web_interface/origin_guard.py +++ b/web_interface/origin_guard.py @@ -116,7 +116,9 @@ def check_request_origin(): if claimed is None: return f'{header} header is not a valid http(s) URL' if claimed != _request_host_port(): - return f'{header} {value!r} is not this interface ({request.host!r})' + # The claimed value is attacker-chosen: the hook logs it, but the + # reason (echoed in the 403 body) never repeats it. + return header + ' names a different host than this interface' return None @@ -128,8 +130,10 @@ def init_app(app: Flask) -> None: reason = check_request_origin() if reason is None: return None - logger.warning("Refused cross-site %s %s: %s", - request.method, request.path, reason) + logger.warning("Refused cross-site %s %s: %s (Origin=%r, Referer=%r)", + request.method, request.path, reason, + request.headers.get('Origin'), + request.headers.get('Referer')) return jsonify({ 'status': 'error', 'error_code': 'CROSS_SITE_REQUEST',