From f4d2aea432e59f43ee4a28c33b65da73a9cc5e18 Mon Sep 17 00:00:00 2001 From: hardik-devops Date: Wed, 1 Jul 2026 19:39:27 +0530 Subject: [PATCH] resolve(proxy): recompute Content-Length when repeat_request replaces body Problem: apply_modifications() let modifications={"body": ...} replace the request body but left the original Content-Length header untouched. build_raw_request() only fills in Content-Length when the header is absent, so the stale, smaller value from the original request was sent on the wire alongside the new, larger body. Impact: A server reading exactly Content-Length bytes truncates the replayed body (or stalls waiting for more), silently discarding the modified/injected payload. This defeats parameter-tampering and injection replays, which is the core use case of repeat_request, and produces misleading negative results during testing. Resolution: When modifications["body"] is present, drop any existing Content-Length header from the carried-over headers (matched case-insensitively), unless the caller explicitly supplies Content-Length in this same call's modifications["headers"]. This lets build_raw_request's existing "fill in if absent" logic recompute the correct length from the new body. Testing: Added tests/test_caido_api.py covering: stale header dropped on body replacement, explicit caller-supplied Content-Length respected, headers left untouched when body isn't modified, and an end-to-end check that build_raw_request emits the recomputed length in the raw HTTP request. Verified RED (failing on pre-fix code, reproducing the exact stale Content-Length: 3 scenario from the bug report) before GREEN. Resolves #603 --- strix/tools/proxy/caido_api.py | 6 ++++ tests/test_caido_api.py | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 tests/test_caido_api.py diff --git a/strix/tools/proxy/caido_api.py b/strix/tools/proxy/caido_api.py index 926d5c74..525be944 100644 --- a/strix/tools/proxy/caido_api.py +++ b/strix/tools/proxy/caido_api.py @@ -261,6 +261,12 @@ def apply_modifications( headers.update(modifications["headers"]) if "body" in modifications: body = modifications["body"] + explicit_content_length = "Content-Length" in { + k.title() for k in modifications.get("headers", {}) + } + if not explicit_content_length: + 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_caido_api.py b/tests/test_caido_api.py new file mode 100644 index 00000000..e287f6de --- /dev/null +++ b/tests/test_caido_api.py @@ -0,0 +1,62 @@ +"""Tests for request modification in the Caido proxy integration.""" + +from __future__ import annotations + +from strix.tools.proxy.caido_api import apply_modifications, build_raw_request + + +def _components(body: str = "old", headers: dict[str, str] | None = None) -> dict: + return { + "method": "POST", + "url_path": "/submit", + "headers": headers or {"Content-Length": str(len(body))}, + "body": body, + } + + +def test_apply_modifications_drops_stale_content_length_on_body_replace() -> None: + components = _components(body="old") + result = apply_modifications( + components, + {"body": "a much longer replacement body than the original"}, + "http://example.com/submit", + ) + assert "Content-Length" not in {k.title() for k in result["headers"]} + + +def test_apply_modifications_keeps_explicit_content_length_on_body_replace() -> None: + components = _components(body="old") + result = apply_modifications( + components, + {"body": "new-body", "headers": {"Content-Length": "8"}}, + "http://example.com/submit", + ) + assert result["headers"]["Content-Length"] == "8" + + +def test_apply_modifications_without_body_change_keeps_headers() -> None: + components = _components(body="old") + result = apply_modifications( + components, + {"headers": {"X-Test": "1"}}, + "http://example.com/submit", + ) + assert result["headers"]["Content-Length"] == "3" + + +def test_build_raw_request_recomputes_content_length_after_replacement() -> None: + components = _components(body="old") + new_body = "a much longer replacement body than the original" + modified = apply_modifications( + components, + {"body": new_body}, + "http://example.com/submit", + ) + _, raw = build_raw_request( + method=modified["method"], + url=modified["url"], + headers=modified["headers"], + body=modified["body"], + ) + head = raw.decode("utf-8").split("\r\n\r\n", 1)[0] + assert f"Content-Length: {len(new_body.encode('utf-8'))}" in head