From 587dc8b27566767e3c0d9c51707e5e9b24c5a867 Mon Sep 17 00:00:00 2001 From: Osamaali313 <86572800+Osamaali313@users.noreply.github.com> Date: Mon, 29 Jun 2026 22:00:25 +0300 Subject: [PATCH 1/2] fix(proxy): recompute Content-Length when repeat_request replaces the body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build_raw_request only sets Content-Length when absent, so a repeat_request that replaces the body kept the original request's stale Content-Length and sent a wrong body length — truncating the modified/injected payload (which defeats parameter-tampering and injection replays). apply_modifications now drops a carried-over Content-Length (case-insensitively) on body replacement so it is recomputed, unless the caller explicitly set Content-Length in the same call. Adds tests. --- strix/tools/proxy/caido_api.py | 8 +++++ tests/test_proxy_caido_api.py | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 tests/test_proxy_caido_api.py diff --git a/strix/tools/proxy/caido_api.py b/strix/tools/proxy/caido_api.py index 926d5c74..5ce03478 100644 --- a/strix/tools/proxy/caido_api.py +++ b/strix/tools/proxy/caido_api.py @@ -261,6 +261,14 @@ def apply_modifications( headers.update(modifications["headers"]) if "body" in modifications: body = modifications["body"] + # Replacing the body invalidates any carried-over Content-Length. + # Drop it (case-insensitively) so build_raw_request recomputes the + # correct length for the new body — unless the caller deliberately + # set Content-Length in this call's header modifications. + explicit_cl = {k.title() for k in modifications.get("headers", {})} + if "Content-Length" not in explicit_cl: + for key in [k for k in headers if k.title() == "Content-Length"]: + del headers[key] if "cookies" in modifications: cookies: dict[str, str] = {} if headers.get("Cookie"): diff --git a/tests/test_proxy_caido_api.py b/tests/test_proxy_caido_api.py new file mode 100644 index 00000000..3299db18 --- /dev/null +++ b/tests/test_proxy_caido_api.py @@ -0,0 +1,59 @@ +"""Tests for raw-request building in the Caido proxy helper.""" + +from __future__ import annotations + +from strix.tools.proxy.caido_api import apply_modifications, build_raw_request + + +def _content_length(raw: bytes) -> str | None: + for line in raw.decode("utf-8").split("\r\n"): + if line.lower().startswith("content-length:"): + return line.split(":", 1)[1].strip() + return None + + +def test_body_replacement_recomputes_content_length() -> None: + components = { + "method": "POST", + "headers": {"Host": "x.test", "Content-Length": "3"}, + "body": "old", + } + new_body = "this is the new and much longer body!" + result = apply_modifications(components, {"body": new_body}, "http://x.test/submit") + _conn, raw = build_raw_request( + method=result["method"], + url=result["url"], + headers=result["headers"], + body=result["body"], + ) + assert _content_length(raw) == str(len(new_body.encode("utf-8"))) + + +def test_explicit_content_length_override_is_preserved() -> None: + components = { + "method": "POST", + "headers": {"Host": "x.test", "Content-Length": "3"}, + "body": "old", + } + result = apply_modifications( + components, + {"body": "new body", "headers": {"Content-Length": "999"}}, + "http://x.test/", + ) + _conn, raw = build_raw_request( + method=result["method"], + url=result["url"], + headers=result["headers"], + body=result["body"], + ) + assert _content_length(raw) == "999" + + +def test_no_body_modification_keeps_content_length() -> None: + components = { + "method": "POST", + "headers": {"Host": "x.test", "Content-Length": "3"}, + "body": "old", + } + result = apply_modifications(components, {"headers": {"X-Test": "1"}}, "http://x.test/") + assert result["headers"].get("Content-Length") == "3" From cd415e7d4d2d25f365c898e200450e8df686145d Mon Sep 17 00:00:00 2001 From: Osamaali313 <86572800+Osamaali313@users.noreply.github.com> Date: Mon, 29 Jun 2026 22:19:13 +0300 Subject: [PATCH 2/2] fix(proxy): set Content-Length: 0 on empty-body replacement build_raw_request only auto-adds Content-Length for a non-empty body, so replacing the body with an empty string produced a request with no Content-Length. Set it to 0 explicitly in apply_modifications (scoped to the body-replacement path, so body-less requests like GET are unaffected). Adds a test. --- strix/tools/proxy/caido_api.py | 4 ++++ tests/test_proxy_caido_api.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/strix/tools/proxy/caido_api.py b/strix/tools/proxy/caido_api.py index 5ce03478..bb7a5bff 100644 --- a/strix/tools/proxy/caido_api.py +++ b/strix/tools/proxy/caido_api.py @@ -269,6 +269,10 @@ def apply_modifications( if "Content-Length" not in explicit_cl: for key in [k for k in headers if k.title() == "Content-Length"]: del headers[key] + # build_raw_request only auto-adds Content-Length for a non-empty + # body, so an empty replacement body needs it set explicitly. + if body == "": + headers["Content-Length"] = "0" if "cookies" in modifications: cookies: dict[str, str] = {} if headers.get("Cookie"): diff --git a/tests/test_proxy_caido_api.py b/tests/test_proxy_caido_api.py index 3299db18..dde2fbd8 100644 --- a/tests/test_proxy_caido_api.py +++ b/tests/test_proxy_caido_api.py @@ -57,3 +57,19 @@ def test_no_body_modification_keeps_content_length() -> None: } result = apply_modifications(components, {"headers": {"X-Test": "1"}}, "http://x.test/") assert result["headers"].get("Content-Length") == "3" + + +def test_empty_body_replacement_sets_content_length_zero() -> None: + components = { + "method": "POST", + "headers": {"Host": "x.test", "Content-Length": "3"}, + "body": "old", + } + result = apply_modifications(components, {"body": ""}, "http://x.test/") + _conn, raw = build_raw_request( + method=result["method"], + url=result["url"], + headers=result["headers"], + body=result["body"], + ) + assert _content_length(raw) == "0"