Skip to content

Commit bafd4de

Browse files
authored
Merge branch 'main' into ds-params
2 parents 2150369 + 616e291 commit bafd4de

86 files changed

Lines changed: 2855 additions & 1359 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/instructions/scenarios.instructions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ The default implementation:
163163
`AttackTechniqueRegistry` singleton)
164164
2. Iterates over every (technique × dataset) pair from `self._dataset_config`
165165
3. Calls `factory.create()` with `objective_target` and conditional scorer override
166+
(also forwards any per-technique converters from `self._strategy_converters`, populated
167+
from the CLI `--strategies <technique>:converter.<name>` modifier, as `extra_request_converters`)
166168
4. Uses `self._build_display_group()` for user-facing grouping
167169
5. Builds `AtomicAttack` with unique `atomic_attack_name` = `"{technique}_{dataset}"`
168170

doc/references.bib

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,12 @@ @misc{odin2024
9999
@article{inie2025summon,
100100
title = {Summon a Demon and Bind it: A Grounded Theory of {LLM} Red Teaming},
101101
author = {Nanna Inie and Jonathan Stray and Leon Derczynski},
102-
journal = {PLoS ONE},
102+
journal = {PLOS ONE},
103+
volume = {20},
104+
number = {1},
105+
pages = {e0314658},
103106
year = {2025},
104-
url = {https://arxiv.org/abs/2311.06237},
107+
url = {https://doi.org/10.1371/journal.pone.0314658},
105108
}
106109

107110
@misc{vantaylor2024socialbias,
@@ -633,17 +636,19 @@ @article{rottger2025msts
633636
url = {https://arxiv.org/abs/2501.10057},
634637
}
635638

636-
@article{zong2024vlguard,
639+
@inproceedings{zong2024vlguard,
637640
title = {Safety Fine-Tuning at (Almost) No Cost: A Baseline for Vision Large Language Models},
638641
author = {Yongshuo Zong and Ondrej Bohdal and Tingyang Yu and Yongxin Yang and Timothy Hospedales},
639-
journal = {arXiv preprint arXiv:2402.02207},
642+
booktitle = {Proceedings of the 41st International Conference on Machine Learning (ICML)},
643+
pages = {62867--62891},
640644
year = {2024},
641-
url = {https://arxiv.org/abs/2402.02207},
645+
publisher = {PMLR},
646+
url = {https://proceedings.mlr.press/v235/zong24a.html},
642647
}
643648

644649
@article{lopez2024pyrit,
645650
title = {{PyRIT}: A Framework for Security Risk Identification and Red Teaming in Generative {AI} Systems},
646-
author = {Gary D. Lopez Munoz and Amanda J. Minnich and Roman Lutz and Richard Lundeen and Raja Sekhar Rao Dheekonda and Nina Chikanov and Bolor-Erdene Jagdagdorj and Martin Pouliot and Shiven Chawla and Whitney Maxwell and Blake Bullwinkel and Katherine Pratt and Joris de Gruyter and Charlotte Siska and Pete Bryan and Tori Westerhoff and Chang Kawaguchi and Christian Seifert and Ram Shankar Siva Kumar and Yonatan Zunger},
651+
author = {Gary D. {Lopez Munoz} and Amanda J. Minnich and Roman Lutz and Richard Lundeen and Raja Sekhar Rao Dheekonda and Nina Chikanov and Bolor-Erdene Jagdagdorj and Martin Pouliot and Shiven Chawla and Whitney Maxwell and Blake Bullwinkel and Katherine Pratt and Joris de Gruyter and Charlotte Siska and Pete Bryan and Tori Westerhoff and Chang Kawaguchi and Christian Seifert and Ram Shankar Siva Kumar and Yonatan Zunger},
647652
journal = {arXiv preprint arXiv:2410.02828},
648653
year = {2024},
649654
url = {https://arxiv.org/abs/2410.02828},
@@ -667,12 +672,13 @@ @inproceedings{wang2025siuo
667672
note = {Introduces the {SIUO} (Safe Inputs but Unsafe Output) benchmark},
668673
}
669674

670-
@misc{darkbench2025,
671-
title = {{DarkBench}: A Comprehensive Benchmark for Dark Design Patterns in Large Language Models},
672-
author = {{Apart Research}},
675+
@inproceedings{darkbench2025,
676+
title = {{DarkBench}: Benchmarking Dark Patterns in Large Language Models},
677+
author = {Esben Kran and Hieu Minh Nguyen and Akash Kundu and Sami Jawhar and Jinsuk Park and Mateusz Maria Jurewicz},
678+
booktitle = {International Conference on Learning Representations (ICLR)},
673679
year = {2025},
674-
url = {https://darkbench.ai/},
675-
note = {OpenReview: https://openreview.net/forum?id=odjMSBSWRt},
680+
url = {https://arxiv.org/abs/2503.10728},
681+
note = {Oral presentation at ICLR 2025},
676682
}
677683

678684
@misc{embracethered2025sneakybits,

doc/scanner/1_pyrit_scan.ipynb

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,36 @@
163163
"cell_type": "markdown",
164164
"id": "9",
165165
"metadata": {},
166+
"source": [
167+
"#### Attaching Converters to a Technique\n",
168+
"\n",
169+
"Strategies (techniques) can have a registered converter instance appended to them with the\n",
170+
"`<technique>:converter.<name>` syntax. The converter is added to the request side of every attack\n",
171+
"the technique produces, on top of any converters the technique already bakes in. This also works on\n",
172+
"aggregate strategies (the converter is applied to every technique the aggregate expands to).\n",
173+
"\n",
174+
"First discover the registered converter instances with `--list-converters` (converters are\n",
175+
"registered by initializers, so pass the same `--initializers`/`--initialization-scripts` you use to run):\n",
176+
"\n",
177+
"```shell\n",
178+
"pyrit_scan --list-converters --initializers my_converters\n",
179+
"```\n",
180+
"\n",
181+
"Then reference a converter by name in `--strategies`:\n",
182+
"\n",
183+
"```shell\n",
184+
"# Add the registered \"translation_spanish\" converter to role_play only\n",
185+
"pyrit_scan airt.rapid_response --target openai_chat --initializers load_default_datasets target my_converters --strategies role_play:converter.translation_spanish\n",
186+
"\n",
187+
"# Chain multiple converters (applied in order) and combine with plain strategies\n",
188+
"pyrit_scan airt.rapid_response --target openai_chat --initializers load_default_datasets target my_converters --strategies role_play:converter.translation_spanish:converter.base64 many_shot\n",
189+
"```"
190+
]
191+
},
192+
{
193+
"cell_type": "markdown",
194+
"id": "10",
195+
"metadata": {},
166196
"source": [
167197
"#### Using Custom Scenarios\n",
168198
"\n",
@@ -172,7 +202,7 @@
172202
{
173203
"cell_type": "code",
174204
"execution_count": null,
175-
"id": "10",
205+
"id": "11",
176206
"metadata": {
177207
"lines_to_next_cell": 2
178208
},
@@ -225,7 +255,7 @@
225255
},
226256
{
227257
"cell_type": "markdown",
228-
"id": "11",
258+
"id": "12",
229259
"metadata": {},
230260
"source": [
231261
"Then discover and run it:\n",

doc/scanner/1_pyrit_scan.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,31 @@
114114
# pyrit_scan garak.encoding --initialization-scripts ./my_custom_config.py
115115
# ```
116116

117+
# %% [markdown]
118+
# #### Attaching Converters to a Technique
119+
#
120+
# Strategies (techniques) can have a registered converter instance appended to them with the
121+
# `<technique>:converter.<name>` syntax. The converter is added to the request side of every attack
122+
# the technique produces, on top of any converters the technique already bakes in. This also works on
123+
# aggregate strategies (the converter is applied to every technique the aggregate expands to).
124+
#
125+
# First discover the registered converter instances with `--list-converters` (converters are
126+
# registered by initializers, so pass the same `--initializers`/`--initialization-scripts` you use to run):
127+
#
128+
# ```shell
129+
# pyrit_scan --list-converters --initializers my_converters
130+
# ```
131+
#
132+
# Then reference a converter by name in `--strategies`:
133+
#
134+
# ```shell
135+
# # Add the registered "translation_spanish" converter to role_play only
136+
# pyrit_scan airt.rapid_response --target openai_chat --initializers load_default_datasets target my_converters --strategies role_play:converter.translation_spanish
137+
#
138+
# # Chain multiple converters (applied in order) and combine with plain strategies
139+
# pyrit_scan airt.rapid_response --target openai_chat --initializers load_default_datasets target my_converters --strategies role_play:converter.translation_spanish:converter.base64 many_shot
140+
# ```
141+
117142
# %% [markdown]
118143
# #### Using Custom Scenarios
119144
#

doc/scanner/2_pyrit_shell.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Once starting the shell, you will see the list of commands you have access to. S
4040
| `list-scenarios` | List all available scenarios |
4141
| `list-initializers` | List all available initializers |
4242
| `list-targets` | List all available targets from the registry |
43+
| `list-converters` | List all registered converter instances |
4344
| `run <scenario> [options]` | Run a scenario with optional parameters |
4445
| `scenario-history` | List all previous scenario runs in this session |
4546
| `print-scenario [N]` | Print detailed results for scenario run(s) |
@@ -65,6 +66,21 @@ pyrit> run garak.encoding --target my_target --initializers target --strategies
6566
pyrit> run foundry.red_team_agent --target my_target --initializers target -s jailbreak crescendo
6667
```
6768

69+
### Attaching Converters to a Technique
70+
71+
Append a registered converter instance to a single technique (or an aggregate strategy) with the
72+
`<technique>:converter.<name>` syntax. The converter is added to the request side of every attack
73+
the technique produces, on top of any converters the technique already bakes in. Use
74+
`list-converters` to discover the registered converter names:
75+
76+
```bash
77+
# Add the registered "translation_spanish" converter to role_play only
78+
pyrit> run airt.rapid_response --target my_target --initializers target load_default_datasets -s role_play:converter.translation_spanish
79+
80+
# Chain multiple converters (applied in order) and combine with plain strategies
81+
pyrit> run airt.rapid_response --target my_target --initializers target load_default_datasets -s role_play:converter.translation_spanish:converter.base64 many_shot
82+
```
83+
6884
### With Runtime Parameters
6985

7086
```bash

frontend/e2e/errors.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,29 @@ test.describe("Error: backend 500 on send message", () => {
264264
// The failed text should be restored in the input for easy re-send
265265
await expect(input).toHaveValue("This should fail", { timeout: 5000 });
266266
});
267+
268+
test("should recover cleanly when the first send fails", async ({ page }) => {
269+
await mockAllAPIs(page, async (route) => {
270+
await route.fulfill({
271+
status: 500,
272+
contentType: "application/json",
273+
body: JSON.stringify({ detail: "Internal server error" }),
274+
});
275+
});
276+
277+
await page.goto("/");
278+
await activateMockTarget(page);
279+
280+
const input = page.getByRole("textbox");
281+
await input.fill("First send fails");
282+
await page.getByRole("button", { name: /send/i }).click();
283+
284+
await expect(page.getByText(/Internal server error/i)).toBeVisible({
285+
timeout: 10000,
286+
});
287+
await expect(page.getByTestId("loading-state")).toHaveCount(0);
288+
await expect(input).toHaveValue("First send fails", { timeout: 5000 });
289+
});
267290
});
268291

269292
// ---------------------------------------------------------------------------

frontend/src/components/Chat/ChatWindow.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,21 @@ export default function ChatWindow({
347347
setLoadedConversationId(effectiveConvId!)
348348
}
349349
} catch (err) {
350+
const viewedConversationId = viewedConvRef.current
351+
const isViewingFailedConversation = viewedConversationId === sendConvId
352+
|| viewedConversationId === (activeConversationId ?? conversationId)
353+
|| (viewedConversationId == null && sendConvId !== '__pending__')
354+
350355
// Only show error in UI if user is still on this conversation
351-
if (viewedConvRef.current === sendConvId || viewedConvRef.current === (activeConversationId ?? conversationId)) {
356+
if (isViewingFailedConversation) {
357+
// Mark the viewed conversation as loaded so first-send failures do not
358+
// get stuck behind the "Loading conversation..." placeholder.
359+
if (viewedConversationId) {
360+
setLoadedConversationId(viewedConversationId)
361+
} else if (sendConvId !== '__pending__') {
362+
setLoadedConversationId(sendConvId)
363+
}
364+
352365
const apiError = toApiError(err)
353366
let description: string
354367
if (apiError.isNetworkError) {

pyrit/backend/models/targets.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
directly.
1010
"""
1111

12-
from typing import Any, Literal
12+
from typing import Literal
1313

1414
from pydantic import BaseModel, Field
1515

1616
from pyrit.backend.models.common import PaginationInfo
17+
from pyrit.models import JSONValue
1718
from pyrit.models.catalog.target import TargetInstance
1819

1920
__all__ = [
@@ -33,7 +34,7 @@ class CreateTargetRequest(BaseModel):
3334
"""Request to create a new target instance."""
3435

3536
type: str = Field(..., description="Target type (e.g., 'OpenAIChatTarget')")
36-
params: dict[str, Any] = Field(default_factory=dict, description="Target constructor parameters")
37+
params: dict[str, JSONValue] = Field(default_factory=dict, description="Target constructor parameters")
3738
auth_mode: Literal["api_key", "entra"] = Field(
3839
"api_key",
3940
description=(

0 commit comments

Comments
 (0)