diff --git a/garak/probes/base.py b/garak/probes/base.py index 5f6412fb1..378f3161e 100644 --- a/garak/probes/base.py +++ b/garak/probes/base.py @@ -393,6 +393,12 @@ def probe(self, generator) -> Iterable[garak.attempt.Attempt]: prompts = copy.deepcopy( self.prompts ) # make a copy to avoid mutating source list + if not prompts: + # A probe can have an empty prompt set (e.g. translation empties it); + # the progress bar below tolerates zero but the `prompts[0]` check + # does not. Produce no attempts instead of raising IndexError (#2026). + logging.warning("probe %s has an empty prompt set", self.probename) + return [] preparation_bar = tqdm.tqdm( total=len(prompts), leave=False, diff --git a/tests/probes/test_probes.py b/tests/probes/test_probes.py index d5a05347b..606798bb5 100644 --- a/tests/probes/test_probes.py +++ b/tests/probes/test_probes.py @@ -5,6 +5,7 @@ import langcodes import pytest import re +from unittest.mock import MagicMock from garak import _config, _plugins from garak.attempt import Turn, Conversation, Message, Attempt @@ -223,6 +224,23 @@ def test_mint_attempt(prompt): assert attempt.prompt.last_message().text == "test example" +def test_probe_with_empty_prompts_returns_no_attempts(): + """An empty (or translation-emptied) prompt set must not IndexError. + + Regression test for #2026: ``probe()`` indexed ``prompts[0]`` before + checking the list was non-empty, so a probe whose prompt set came back empty + raised ``IndexError`` instead of producing no attempts. + """ + import garak.probes.base + + probe = garak.probes.base.Probe() + probe.prompts = [] + + attempts = probe.probe(MagicMock()) + + assert attempts == [] + + @pytest.mark.parametrize("prompt", PROMPT_EXAMPLES) def test_mint_attempt_with_run_system_prompt(prompt): import garak.probes.base @@ -361,4 +379,3 @@ def test_encoding_probe_attempt_carries_payload_intent(loaded_intent_service): attempt.intent == expected_intent ), "attempt intent must reflect the payload-specific intent set in _attempt_prestore_hook" -