From faf4344f9762127c3652f3087d67bae77b9cae94 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 27 May 2026 09:17:19 -0400 Subject: [PATCH] fix: strip Content-Length before sending to avoid LocalProtocolError Removes Content-Length from request headers before sending with httpx to prevent LocalProtocolError when placeholder replacement (e.g. <>) changes the body size. httpx calculates the correct Content-Length from the actual content. Closes #139 --- agentic_security/http_spec.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/agentic_security/http_spec.py b/agentic_security/http_spec.py index d59a398..7aa5645 100644 --- a/agentic_security/http_spec.py +++ b/agentic_security/http_spec.py @@ -69,9 +69,7 @@ class LLMSpec(BaseModel): return response - def validate( - self, prompt: str, encoded_image: str, encoded_audio: str, files: dict | None - ) -> None: + def validate(self, prompt: str, encoded_image: str, encoded_audio: str, files: dict | None) -> None: if self.has_files and not files: raise ValueError("Files are required for this request.") @@ -107,12 +105,17 @@ class LLMSpec(BaseModel): content = content.replace("<>", encoded_image) content = content.replace("<>", encoded_audio) + # Remove Content-Length from headers to avoid mismatch when + # placeholder replacement changes body size. httpx will set + # the correct Content-Length based on the actual content. + clean_headers = {k: v for k, v in self.headers.items() if k.lower() != "content-length"} + transport = httpx.AsyncHTTPTransport(retries=settings_var("network.retry", 3)) async with httpx.AsyncClient(transport=transport) as client: response = await client.request( method=self.method, url=self.url, - headers=self.headers, + headers=clean_headers, content=content, timeout=self.timeout(), ) @@ -127,9 +130,7 @@ class LLMSpec(BaseModel): return await self.probe( "test", # TODO: fix url for mp3 - encoded_audio=encode_audio_base64_by_url( - "https://www.example.com/audio.mp3" - ), + encoded_audio=encode_audio_base64_by_url("https://www.example.com/audio.mp3"), ) case LLMSpec(has_files=True): return await self._probe_with_files({}) @@ -168,18 +169,14 @@ def parse_http_spec(http_spec: str) -> LLMSpec: # Extract the method and URL from the first line request_line_parts = lines[0].split() if len(request_line_parts) < 2: - raise InvalidHTTPSpecError( - "First line of HTTP spec must include the method and URL." - ) + raise InvalidHTTPSpecError("First line of HTTP spec must include the method and URL.") method, url = request_line_parts[0], request_line_parts[1] # Check url validity valid_url = urlparse(url) # if missing the correct formatting ://, urlparse.netloc will be empty if valid_url.scheme not in ("http", "https") or not valid_url.netloc: - raise InvalidHTTPSpecError( - f"Invalid URL: {url}. Ensure it starts with 'http://' or 'https://'" - ) + raise InvalidHTTPSpecError(f"Invalid URL: {url}. Ensure it starts with 'http://' or 'https://'") # Initialize headers and body headers = {}