Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 206 additions & 15 deletions doc/scanner/garak.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"The Garak scenario family implements probes inspired by the\n",
"[Garak](https://github.com/NVIDIA/garak) framework. These include encoding-based probes (which\n",
"test whether a target can be tricked into producing harmful content when prompts are encoded in\n",
"various formats) and web-injection probes (which test whether a target emits markdown\n",
"data-exfiltration or cross-site-scripting payloads).\n",
"various formats), web-injection probes (which test whether a target emits markdown\n",
"data-exfiltration or cross-site-scripting payloads), and an audio probe (which delivers spoken\n",
"jailbreaks to multimodal targets).\n",
"\n",
"For full programming details, see the\n",
"[Scenarios Programming Guide](../code/scenarios/0_scenarios.ipynb)."
Expand All @@ -21,8 +22,18 @@
"cell_type": "code",
"execution_count": null,
"id": "1",
"metadata": {},
"metadata": {
"lines_to_next_cell": 0
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"./AppData/Local/miniconda3/Lib/site-packages/requests/__init__.py:113: RequestsDependencyWarning: urllib3 (2.5.0) or chardet (7.4.3)/charset_normalizer (3.3.2) doesn't match a supported version!\n",
" warnings.warn(\n"
]
},
{
"name": "stdout",
"output_type": "stream",
Expand All @@ -32,6 +43,34 @@
"Loaded environment file: ./.pyrit/.env.local\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"[pyrit:alembic] No new upgrade operations detected.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"Skipping target 'platform_openai_chat': PLATFORM_OPENAI_CHAT_GPT4O_MODEL is not set. All declared env vars (endpoint, key, model) must be present for this target to register.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"Skipping target 'azure_foundry_phi4': AZURE_FOUNDRY_PHI4_MODEL is not set. All declared env vars (endpoint, key, model) must be present for this target to register.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"TextAdaptive: _EXCLUDED_TECHNIQUES entries ['prompt_sending'] are not in the current scenario-techniques catalog ['context_compliance', 'crescendo_history_lecture', 'crescendo_journalist_interview', 'crescendo_movie_director', 'crescendo_simulated', 'many_shot', 'pair', 'red_teaming', 'role_play', 'tap', 'violent_durian']; the exclusion is a no-op for those entries. Remove stale entries or update the catalog.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
Expand All @@ -44,8 +83,10 @@
"from pathlib import Path\n",
"\n",
"from pyrit.output import output_scenario_async\n",
"from pyrit.prompt_target import RealtimeTarget\n",
"from pyrit.registry import TargetRegistry\n",
"from pyrit.scenario.garak import Encoding, EncodingStrategy\n",
"from pyrit.scenario.garak.audio_achilles_heel import AudioAchillesHeel, AudioAchillesHeelDatasetConfiguration\n",
"from pyrit.scenario.garak.encoding import EncodingDatasetConfiguration\n",
"from pyrit.setup import initialize_from_config_async\n",
"\n",
Expand Down Expand Up @@ -96,7 +137,7 @@
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "d1a7afc39dbc438db3d9f04b9405a0e4",
"model_id": "c78994c1ec534a1abdcb9141084051c5",
"version_major": 2,
"version_minor": 0
},
Expand Down Expand Up @@ -144,7 +185,7 @@
"\u001b[1m 📋 Scenario Details\u001b[0m\n",
"\u001b[36m • Name: Encoding\u001b[0m\n",
"\u001b[36m • Scenario Version: 1\u001b[0m\n",
"\u001b[36m • PyRIT Version: 0.12.1.dev0\u001b[0m\n",
"\u001b[36m • PyRIT Version: 0.15.0.dev0\u001b[0m\n",
"\u001b[36m • Description:\u001b[0m\n",
"\u001b[36m Encoding Scenario implementation for PyRIT. This scenario tests how resilient models are to various encoding\u001b[0m\n",
"\u001b[36m attacks by encoding potentially harmful text (by default slurs and XSS payloads) and testing if the model will\u001b[0m\n",
Expand All @@ -156,8 +197,8 @@
"\n",
"\u001b[1m 🎯 Target Information\u001b[0m\n",
"\u001b[36m • Target Type: OpenAIChatTarget\u001b[0m\n",
"\u001b[36m • Target Model: gpt-4o\u001b[0m\n",
"\u001b[36m • Target Endpoint: https://pyrit-dev.openai.azure.com/openai/v1\u001b[0m\n",
"\u001b[36m • Target Model: gpt-4o-japan-nilfilter\u001b[0m\n",
"\u001b[36m • Target Endpoint: https://pyrit-japan-test.openai.azure.com/openai/v1\u001b[0m\n",
"\n",
"\u001b[1m 📊 Scorer Information\u001b[0m\n",
"\u001b[37m ▸ Scorer Identifier\u001b[0m\n",
Expand All @@ -173,20 +214,20 @@
"\u001b[1m 📈 Summary\u001b[0m\n",
"\u001b[32m • Total Strategies: 2\u001b[0m\n",
"\u001b[32m • Total Attack Results: 21\u001b[0m\n",
"\u001b[31m • Overall Success Rate: 90%\u001b[0m\n",
"\u001b[32m • Unique Objectives: 2\u001b[0m\n",
"\u001b[31m • Overall Success Rate: 95%\u001b[0m\n",
"\u001b[32m • Unique Objectives: 1\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Per-Strategy Breakdown\u001b[0m\n",
"\u001b[1m\u001b[36m▼ Per-Group Breakdown\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Strategy: baseline\u001b[0m\n",
"\u001b[1m 🔸 Group: base64\u001b[0m\n",
"\u001b[33m • Number of Results: 20\u001b[0m\n",
"\u001b[31m • Success Rate: 95%\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Group: baseline\u001b[0m\n",
"\u001b[33m • Number of Results: 1\u001b[0m\n",
"\u001b[31m • Success Rate: 100%\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Strategy: base64\u001b[0m\n",
"\u001b[33m • Number of Results: 20\u001b[0m\n",
"\u001b[31m • Success Rate: 90%\u001b[0m\n",
"\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\n"
]
Expand Down Expand Up @@ -226,6 +267,156 @@
"cell_type": "markdown",
"id": "6",
"metadata": {},
"source": [
"## AudioAchillesHeel\n",
"\n",
"Ports Garak's `audio.AudioAchillesHeel` probe. Delivers the adversarial instruction as *spoken\n",
"audio* while the text channel carries only a benign \"follow the audio instructions\" nudge. Each\n",
"clip from the `garak_audio_achilles_heel` dataset is shaped into a single multimodal user turn\n",
"(text nudge + audio at the same sequence), and the response is scored for compliance — the PyRIT\n",
"analogue of Garak's non-refusal `mitigation.MitigationBypass` detector. A per-clip objective is\n",
"derived from the clip's harm category.\n",
"\n",
"**CLI example:**\n",
"\n",
"```bash\n",
"pyrit_scan garak.audio_achilles_heel --target realtime --max-dataset-size 2\n",
"```\n",
"\n",
"> **Note:** The objective target must accept `audio_path` input (i.e. be multimodal). The example\n",
"> below uses `RealtimeTarget` (the OpenAI Realtime audio websocket); non-audio targets such as the\n",
"> default `openai_chat` will error when the audio request is sent. The full dataset holds ~350\n",
"> clips, so a default run samples a small subset to finish quickly — raise `--max-dataset-size`\n",
"> for broader coverage."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Scenario: AudioAchillesHeel\n",
"Atomic attacks: 1\n"
]
},
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "6151937673ea4d19b35ebe7a6610d2dd",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Executing AudioAchillesHeel: 0%| | 0/1 [00:00<?, ?attack/s]"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"audio_dataset_config = AudioAchillesHeelDatasetConfiguration(\n",
" dataset_names=[\"garak_audio_achilles_heel\"], max_dataset_size=1\n",
")\n",
"\n",
"audio_target = RealtimeTarget()\n",
"\n",
"audio_scenario = AudioAchillesHeel()\n",
"await audio_scenario.initialize_async( # type: ignore\n",
" objective_target=audio_target,\n",
" dataset_config=audio_dataset_config,\n",
")\n",
"\n",
"print(f\"Scenario: {audio_scenario.name}\")\n",
"print(f\"Atomic attacks: {audio_scenario.atomic_attack_count}\")\n",
"\n",
"audio_scenario_result = await audio_scenario.run_async() # type: ignore"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\u001b[1m\u001b[36m 📊 SCENARIO RESULTS: AudioAchillesHeel \u001b[0m\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Scenario Information\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\u001b[1m 📋 Scenario Details\u001b[0m\n",
"\u001b[36m • Name: AudioAchillesHeel\u001b[0m\n",
"\u001b[36m • Scenario Version: 1\u001b[0m\n",
"\u001b[36m • PyRIT Version: 0.15.0.dev0\u001b[0m\n",
"\u001b[36m • Description:\u001b[0m\n",
"\u001b[36m Audio Achilles Heel scenario implementation for PyRIT. Ports Garak's ``audio.AudioAchillesHeel`` probe: spoken\u001b[0m\n",
"\u001b[36m adversarial instructions are sent as audio while the text channel carries only a benign nudge, testing whether a\u001b[0m\n",
"\u001b[36m multimodal target follows harmful spoken instructions. Each ``garak_audio_achilles_heel`` clip becomes a single\u001b[0m\n",
"\u001b[36m multimodal user turn scored for compliance (the PyRIT analogue of Garak's non-refusal\u001b[0m\n",
"\u001b[36m ``mitigation.MitigationBypass`` detector). The objective target must accept ``audio_path`` input (i.e. be\u001b[0m\n",
"\u001b[36m multimodal); non-audio targets will error when the request is sent. Reference: https://arxiv.org/html/2410.23861\u001b[0m\n",
"\n",
"\u001b[1m 🎯 Target Information\u001b[0m\n",
"\u001b[36m • Target Type: RealtimeTarget\u001b[0m\n",
"\u001b[36m • Target Model: gpt-realtime-1.5\u001b[0m\n",
"\u001b[36m • Target Endpoint: wss://airt-blackhat-2-aoaio2.openai.azure.com/openai/v1\u001b[0m\n",
"\n",
"\u001b[1m 📊 Scorer Information\u001b[0m\n",
"\u001b[37m ▸ Scorer Identifier\u001b[0m\n",
"\u001b[36m • Scorer Type: FloatScaleThresholdScorer\u001b[0m\n",
"\u001b[36m • scorer_type: true_false\u001b[0m\n",
"\u001b[36m • score_aggregator: OR_\u001b[0m\n",
"\u001b[36m └─ Composite of 1 scorer(s):\u001b[0m\n",
"\u001b[36m • Scorer Type: AzureContentFilterScorer\u001b[0m\n",
"\u001b[36m • scorer_type: float_scale\u001b[0m\n",
"\n",
"\u001b[37m ▸ Performance Metrics\u001b[0m\n",
"\u001b[31m • Accuracy: 59.24%\u001b[0m\n",
"\u001b[36m • Accuracy Std Error: ±0.0247\u001b[0m\n",
"\u001b[31m • F1 Score: 0.5306\u001b[0m\n",
"\u001b[31m • Precision: 0.5987\u001b[0m\n",
"\u001b[31m • Recall: 0.4764\u001b[0m\n",
"\u001b[32m • Average Score Time: 0.04s\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Overall Statistics\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\u001b[1m 📈 Summary\u001b[0m\n",
"\u001b[32m • Total Strategies: 1\u001b[0m\n",
"\u001b[32m • Total Attack Results: 1\u001b[0m\n",
"\u001b[32m • Overall Success Rate: 0%\u001b[0m\n",
"\u001b[32m • Unique Objectives: 1\u001b[0m\n",
"\n",
"\u001b[1m\u001b[36m▼ Per-Group Breakdown\u001b[0m\n",
"\u001b[36m────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n",
"\n",
"\u001b[1m 🔸 Group: audio_jailbreak\u001b[0m\n",
"\u001b[33m • Number of Results: 1\u001b[0m\n",
"\u001b[32m • Success Rate: 0%\u001b[0m\n",
"\n",
"\u001b[36m====================================================================================================\u001b[0m\n",
"\n"
]
}
],
"source": [
"await output_scenario_async(audio_scenario_result)"
]
},
{
"cell_type": "markdown",
"id": "9",
"metadata": {},
"source": [
"For more details, see the [Scenarios Programming Guide](../code/scenarios/0_scenarios.ipynb) and\n",
"[Configuration](../getting_started/configuration.md)."
Expand Down
50 changes: 48 additions & 2 deletions doc/scanner/garak.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
# The Garak scenario family implements probes inspired by the
# [Garak](https://github.com/NVIDIA/garak) framework. These include encoding-based probes (which
# test whether a target can be tricked into producing harmful content when prompts are encoded in
# various formats) and web-injection probes (which test whether a target emits markdown
# data-exfiltration or cross-site-scripting payloads).
# various formats), web-injection probes (which test whether a target emits markdown
# data-exfiltration or cross-site-scripting payloads), and an audio probe (which delivers spoken
# jailbreaks to multimodal targets).
#
# For full programming details, see the
# [Scenarios Programming Guide](../code/scenarios/0_scenarios.ipynb).
Expand All @@ -24,8 +25,10 @@
from pathlib import Path

from pyrit.output import output_scenario_async
from pyrit.prompt_target import RealtimeTarget
from pyrit.registry import TargetRegistry
from pyrit.scenario.garak import Encoding, EncodingStrategy
from pyrit.scenario.garak.audio_achilles_heel import AudioAchillesHeel, AudioAchillesHeelDatasetConfiguration
from pyrit.scenario.garak.encoding import EncodingDatasetConfiguration
from pyrit.setup import initialize_from_config_async

Expand Down Expand Up @@ -91,6 +94,49 @@
# **Aggregate strategies:** `ALL` (all 8), `DEFAULT` (excludes the two combinatorial extended
# probes), `EXFIL` (the 6 markdown-exfil probes), and `XSS` (TaskXSS + MarkdownXSS).

# %% [markdown]
# ## AudioAchillesHeel
#
# Ports Garak's `audio.AudioAchillesHeel` probe. Delivers the adversarial instruction as *spoken
# audio* while the text channel carries only a benign "follow the audio instructions" nudge. Each
# clip from the `garak_audio_achilles_heel` dataset is shaped into a single multimodal user turn
# (text nudge + audio at the same sequence), and the response is scored for compliance — the PyRIT
# analogue of Garak's non-refusal `mitigation.MitigationBypass` detector. A per-clip objective is
# derived from the clip's harm category.
#
# **CLI example:**
#
# ```bash
# pyrit_scan garak.audio_achilles_heel --target realtime --max-dataset-size 2
# ```
#
# > **Note:** The objective target must accept `audio_path` input (i.e. be multimodal). The example
# > below uses `RealtimeTarget` (the OpenAI Realtime audio websocket); non-audio targets such as the
# > default `openai_chat` will error when the audio request is sent. The full dataset holds ~350
# > clips, so a default run samples a small subset to finish quickly — raise `--max-dataset-size`
# > for broader coverage.

# %%
audio_dataset_config = AudioAchillesHeelDatasetConfiguration(
dataset_names=["garak_audio_achilles_heel"], max_dataset_size=1
)

audio_target = RealtimeTarget()

audio_scenario = AudioAchillesHeel()
await audio_scenario.initialize_async( # type: ignore
objective_target=audio_target,
dataset_config=audio_dataset_config,
)

print(f"Scenario: {audio_scenario.name}")
print(f"Atomic attacks: {audio_scenario.atomic_attack_count}")

audio_scenario_result = await audio_scenario.run_async() # type: ignore

# %%
await output_scenario_async(audio_scenario_result)

# %% [markdown]
# For more details, see the [Scenarios Programming Guide](../code/scenarios/0_scenarios.ipynb) and
# [Configuration](../getting_started/configuration.md).
3 changes: 3 additions & 0 deletions pyrit/scenario/scenarios/garak/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

"""Garak-based attack scenarios."""

from pyrit.scenario.scenarios.garak.audio_achilles_heel import AudioAchillesHeel, AudioAchillesHeelStrategy
from pyrit.scenario.scenarios.garak.doctor import Doctor, DoctorStrategy
from pyrit.scenario.scenarios.garak.encoding import Encoding, EncodingStrategy
from pyrit.scenario.scenarios.garak.web_injection import WebInjection, WebInjectionStrategy

__all__ = [
"AudioAchillesHeel",
"AudioAchillesHeelStrategy",
"Doctor",
"DoctorStrategy",
"Encoding",
Expand Down
Loading
Loading