diff --git a/garak/detectors/promptinject.py b/garak/detectors/promptinject.py index 8e322d85c..7a1cd3ce1 100644 --- a/garak/detectors/promptinject.py +++ b/garak/detectors/promptinject.py @@ -31,7 +31,6 @@ def detect(self, attempt: Attempt) -> List[float]: results.append(None) continue output_text = output.text - score = 0.0 for trigger in triggers: diff --git a/garak/generators/rest.py b/garak/generators/rest.py index 303369bf2..b7b9f50a2 100644 --- a/garak/generators/rest.py +++ b/garak/generators/rest.py @@ -403,7 +403,7 @@ def _call_model( ) return [None] - return [Message(r) for r in response] + return [Message(str(r) if r is not None and not isinstance(r, str) else r) for r in response] class _MtlsAdapter(requests.adapters.HTTPAdapter): diff --git a/tests/generators/test_rest.py b/tests/generators/test_rest.py index 68798887a..933de181e 100644 --- a/tests/generators/test_rest.py +++ b/tests/generators/test_rest.py @@ -500,3 +500,44 @@ def test_rest_mtls_pickle_roundtrip(real_mtls_cert_files): assert isinstance( adapter, _MtlsAdapter ), "Reconstructed session must have an _MtlsAdapter mounted on 'https://'" + + +@pytest.mark.usefixtures("set_rest_config") +@pytest.mark.parametrize( + "value,expected_text", + [ + (42, "42"), + (3.14, "3.14"), + (True, "True"), + ({"nested": "dict"}, "{'nested': 'dict'}"), + ], +) +def test_json_rest_non_string_response_coerced_to_str(requests_mock, value, expected_text): + # regression for #1888: non-string values from response_json_field must be + # str()-coerced before being wrapped in Message (Message.text is typed str | None). + # The dict case is the original crash: AttackRogueString called .lower() on a dict. + requests_mock.post( + DEFAULT_URI, + text=json.dumps({"result": value}, ensure_ascii=False), + ) + _config.plugins.generators["rest"]["RestGenerator"]["response_json"] = True + _config.plugins.generators["rest"]["RestGenerator"]["response_json_field"] = "result" + generator = RestGenerator() + conv = Conversation([Turn("user", Message("test prompt"))]) + output = generator._call_model(conv) + assert output == [Message(expected_text)] + + +@pytest.mark.usefixtures("set_rest_config") +def test_json_rest_non_string_list_coerced_to_str(requests_mock): + # regression: each element in a multi-value response list must be str()-coerced + requests_mock.post( + DEFAULT_URI, + text=json.dumps({"scores": [1, 2, 3]}, ensure_ascii=False), + ) + _config.plugins.generators["rest"]["RestGenerator"]["response_json"] = True + _config.plugins.generators["rest"]["RestGenerator"]["response_json_field"] = "$.scores[*]" + generator = RestGenerator() + conv = Conversation([Turn("user", Message("test prompt"))]) + output = generator._call_model(conv) + assert output == [Message("1"), Message("2"), Message("3")]