TEST: Preserve Crescendo success for leaky refusals - #2656
Roman Lutz (romanlutz) merged 4 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21bf73f6-4b26-4f5f-bb22-01fa72e49186
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21bf73f6-4b26-4f5f-bb22-01fa72e49186
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21bf73f6-4b26-4f5f-bb22-01fa72e49186
This reverts commit 7b20fe8. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 21bf73f6-4b26-4f5f-bb22-01fa72e49186
Justin Song (jsong468)
left a comment
There was a problem hiding this comment.
A bit confused on this behavior and whether this ever does/should happen. If a refusal is detected and backtracking is available, then the last turn will get erased and we backtrack, which in my head means the attack was not successful. But, if no more backtracking is available, then the attack could be marked successful, meaning the outcome could be variable depending on an axis that theoretically shouldn't have impact on ultimate outcome.
Basically, shouldn't "A target response can include refusal language while still revealing enough harmful information" be graded as True for objective achieved no matter if backtracking is allowed or not (since it's still harmful and I, a red teamer, would want to know about that)? If this is truly something that does happen often in practice, I imagine it might require a structural change (objective scorer kicks in no matter what).
It's an interesting problem. It used to be pretty easy to detect refusal and it was actually always (99.9% of the time) a refusal. Refusals come in different shapes now. Sometimes it's an outright refusal, sometimes there's a long explanation of why the model refuses just to then pivot and help out anyway 🤷🏻 Crescendo has not changed in the meantime, though. Maybe we should just run refusal and other scorers in parallel and only backtrack if "refusal AND not success" while continuing if "not refusal AND not success" and in the success case we end execution? That would change the logic somewhat, though. This case actually came up because GHCP told me the existing behavior (in the last iteration if refusal AND success --> success) is a bug and wanted to fix it. I decided it's not a bug (which is not 100% clear either) and wanted to put in a test case to assert this behavior. But we can bring this up in standup. |
Yea..makes sense! I would agree with having a "if not refusal AND not success" check if refusals and failure have become less synonymous! For now, test as is, is okay and approving! |
Description
A target response can include refusal language while still revealing enough harmful information to achieve the attack objective. Refusal detection must therefore not override the configured objective scorer's verdict.
This adds a regression contract for Crescendo's no-backtrack path: when refusal detection is true and the objective score is true, the attack returns
AttackOutcome.SUCCESSwith the objective-achieved reason. The test also preserves the refusal and backtracking state expectations. There is no production-code change.Tests and Documentation
uv run pytest tests\unit\executor\attack\multi_turn\test_crescendo.py -q(84 passed)uv run ruff check pyrit\executor\attack\multi_turn\crescendo.py tests\unit\executor\attack\multi_turn\test_crescendo.pyuv run ruff format --check pyrit\executor\attack\multi_turn\crescendo.py tests\unit\executor\attack\multi_turn\test_crescendo.pyuv run ty check pyrit\executor\attack\multi_turn\crescendo.py tests\unit\executor\attack\multi_turn\test_crescendo.pyuv run python -m compileall -q pyrit\executor\attack\multi_turn\crescendo.py tests\unit\executor\attack\multi_turn\test_crescendo.pygit diff --checkDocumentation not updated because this PR codifies the existing documented scorer-driven behavior. JupyText was not run because no documentation or notebook files changed.