From 96e137b26f69b69638b91ede2ddd9d07446f86bf Mon Sep 17 00:00:00 2001 From: biefan <70761325+biefan@users.noreply.github.com> Date: Tue, 17 Mar 2026 03:12:22 +0000 Subject: [PATCH 1/3] Preserve query params for HTTPX API requests --- .../http_target/httpx_api_target.py | 2 +- tests/unit/target/test_http_api_target.py | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/pyrit/prompt_target/http_target/httpx_api_target.py b/pyrit/prompt_target/http_target/httpx_api_target.py index bbf993928..f015e482c 100644 --- a/pyrit/prompt_target/http_target/httpx_api_target.py +++ b/pyrit/prompt_target/http_target/httpx_api_target.py @@ -139,7 +139,7 @@ async def send_prompt_async(self, *, message: Message) -> list[Message]: method=self.method, url=self.http_url, headers=self.headers, - params=self.params if self.method in {"GET", "HEAD"} else None, + params=self.params, json=self.json_data if self.method in {"POST", "PUT", "PATCH"} else None, data=self.form_data if self.method in {"POST", "PUT", "PATCH"} else None, follow_redirects=True, diff --git a/tests/unit/target/test_http_api_target.py b/tests/unit/target/test_http_api_target.py index c67919fe1..c8e30525f 100644 --- a/tests/unit/target/test_http_api_target.py +++ b/tests/unit/target/test_http_api_target.py @@ -70,6 +70,36 @@ async def test_send_prompt_async_no_file(mock_request, patch_central_database): assert "Sample JSON response" in response_text +@pytest.mark.asyncio +@patch("httpx.AsyncClient.request") +async def test_send_prompt_async_preserves_query_params_for_post(mock_request, patch_central_database): + message_piece = MessagePiece(role="user", original_value="mock", converted_value="non_existent_file.pdf") + message = Message(message_pieces=[message_piece]) + + mock_response = MagicMock() + mock_response.content = b'{"status": "ok"}' + mock_request.return_value = mock_response + + target = HTTPXAPITarget( + http_url="http://example.com/data/", + method="POST", + params={"alpha": "1"}, + json_data={"payload": "value"}, + timeout=180, + ) + await target.send_prompt_async(message=message) + + mock_request.assert_called_once_with( + method="POST", + url="http://example.com/data/", + headers={}, + params={"alpha": "1"}, + json={"payload": "value"}, + data=None, + follow_redirects=True, + ) + + @pytest.mark.asyncio @patch("httpx.AsyncClient.request") async def test_send_prompt_async_validation(mock_request, patch_central_database): From f0d71773f3938c6c7b496c816f0d59e0122a86f3 Mon Sep 17 00:00:00 2001 From: biefan <70761325+biefan@users.noreply.github.com> Date: Tue, 17 Mar 2026 03:16:58 +0000 Subject: [PATCH 2/3] Preserve query params for uploaded API requests --- .../http_target/httpx_api_target.py | 1 + tests/unit/target/test_http_api_target.py | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/pyrit/prompt_target/http_target/httpx_api_target.py b/pyrit/prompt_target/http_target/httpx_api_target.py index f015e482c..7f40c0a4a 100644 --- a/pyrit/prompt_target/http_target/httpx_api_target.py +++ b/pyrit/prompt_target/http_target/httpx_api_target.py @@ -129,6 +129,7 @@ async def send_prompt_async(self, *, message: Message) -> list[Message]: method=self.method, url=self.http_url, headers=self.headers, + params=self.params, files=files, follow_redirects=True, ) diff --git a/tests/unit/target/test_http_api_target.py b/tests/unit/target/test_http_api_target.py index c8e30525f..30c73fbcb 100644 --- a/tests/unit/target/test_http_api_target.py +++ b/tests/unit/target/test_http_api_target.py @@ -45,6 +45,34 @@ async def test_send_prompt_async_file_upload(mock_request, patch_central_databas os.unlink(file_path) +@pytest.mark.asyncio +@patch("httpx.AsyncClient.request") +async def test_send_prompt_async_file_upload_preserves_query_params(mock_request, patch_central_database): + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write(b"This is a mock PDF content") + tmp.flush() + file_path = tmp.name + + message_piece = MessagePiece(role="user", original_value="mock", converted_value=file_path) + message = Message(message_pieces=[message_piece]) + + mock_response = MagicMock() + mock_response.content = b'{"message": "File uploaded successfully"}' + mock_request.return_value = mock_response + + target = HTTPXAPITarget( + http_url="http://example.com/upload/", + method="POST", + params={"alpha": "1"}, + timeout=180, + ) + await target.send_prompt_async(message=message) + + assert mock_request.call_args.kwargs["params"] == {"alpha": "1"} + + os.unlink(file_path) + + @pytest.mark.asyncio @patch("httpx.AsyncClient.request") async def test_send_prompt_async_no_file(mock_request, patch_central_database): From dcb99bf6db844d18976ccb10b8bb4d5a6ecfdf47 Mon Sep 17 00:00:00 2001 From: biefan <70761325+biefan@users.noreply.github.com> Date: Tue, 17 Mar 2026 03:39:35 +0000 Subject: [PATCH 3/3] Fail fast on missing explicit upload files --- .../http_target/httpx_api_target.py | 2 ++ tests/unit/target/test_http_api_target.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/pyrit/prompt_target/http_target/httpx_api_target.py b/pyrit/prompt_target/http_target/httpx_api_target.py index 7f40c0a4a..393f947ba 100644 --- a/pyrit/prompt_target/http_target/httpx_api_target.py +++ b/pyrit/prompt_target/http_target/httpx_api_target.py @@ -105,6 +105,8 @@ async def send_prompt_async(self, *, message: Message) -> list[Message]: if isinstance(possible_path, str) and os.path.exists(possible_path): logger.info(f"HTTPXApiTarget: auto-using file_path from {possible_path}") self.file_path = possible_path + elif not os.path.exists(self.file_path): + raise FileNotFoundError(f"File not found: {self.file_path}") if not self.http_url: raise ValueError("No `http_url` provided for HTTPXApiTarget.") diff --git a/tests/unit/target/test_http_api_target.py b/tests/unit/target/test_http_api_target.py index 30c73fbcb..ad98884b3 100644 --- a/tests/unit/target/test_http_api_target.py +++ b/tests/unit/target/test_http_api_target.py @@ -128,6 +128,25 @@ async def test_send_prompt_async_preserves_query_params_for_post(mock_request, p ) +@pytest.mark.asyncio +@patch("httpx.AsyncClient.request") +async def test_send_prompt_async_missing_explicit_file_path_raises(mock_request, patch_central_database): + message_piece = MessagePiece(role="user", original_value="mock", converted_value="trigger") + message = Message(message_pieces=[message_piece]) + + target = HTTPXAPITarget( + http_url="http://example.com/upload/", + method="POST", + file_path="/definitely/missing/file.pdf", + timeout=180, + ) + + with pytest.raises(FileNotFoundError, match="File not found"): + await target.send_prompt_async(message=message) + + mock_request.assert_not_called() + + @pytest.mark.asyncio @patch("httpx.AsyncClient.request") async def test_send_prompt_async_validation(mock_request, patch_central_database):