Skip to content

Commit 1434fe4

Browse files
rlundeen2Copilot
andauthored
FEAT: Adding descriptions to Techniques (microsoft#2167)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 4634610 commit 1434fe4

5 files changed

Lines changed: 61 additions & 0 deletions

File tree

pyrit/scenario/core/attack_technique_factory.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def __init__(
7474
*,
7575
name: str,
7676
attack_class: type[AttackStrategy[Any, Any]],
77+
description: str | None = None,
7778
technique_tags: list[str] | None = None,
7879
attack_kwargs: dict[str, Any] | None = None,
7980
adversarial_chat: PromptTarget | None = None,
@@ -90,6 +91,9 @@ def __init__(
9091
name: Registry name for this technique. This is used as the
9192
scenario technique name.
9293
attack_class: The AttackStrategy subclass to instantiate.
94+
description: Short human-readable summary of what the technique does.
95+
Purely descriptive metadata — it does not affect the technique's
96+
behavioral identity.
9397
technique_tags: Tags controlling which ``ScenarioTechnique``
9498
aggregates include this technique (e.g. ``"single_turn"``,
9599
``"multi_turn"``, ``"default"``).
@@ -129,6 +133,7 @@ class constructor signature and seed-technique shape.
129133
"""
130134
self._name = name
131135
self._attack_class = attack_class
136+
self._description = description
132137
self._technique_tags = list(technique_tags) if technique_tags else []
133138
self._attack_kwargs = dict(attack_kwargs) if attack_kwargs else {}
134139
self._adversarial_chat = adversarial_chat
@@ -151,6 +156,7 @@ def with_simulated_conversation(
151156
*,
152157
name: str,
153158
attack_class: type[AttackStrategy[Any, Any]] | None = None,
159+
description: str | None = None,
154160
adversarial_chat_system_prompt_path: str | Path | None = None,
155161
simulated_target_system_prompt_path: str | Path | None = None,
156162
next_message_system_prompt_path: str | Path | None = None,
@@ -175,6 +181,8 @@ def with_simulated_conversation(
175181
``EXECUTOR_SEED_PROMPT_PATH/red_teaming/{name}.yaml``.
176182
attack_class: The AttackStrategy subclass to instantiate. Defaults to
177183
``PromptSendingAttack``.
184+
description: Short human-readable summary of what the technique does.
185+
Forwarded to the factory constructor as descriptive metadata.
178186
adversarial_chat_system_prompt_path: Path to the YAML file containing
179187
the adversarial chat system prompt for the simulated conversation.
180188
Defaults to ``EXECUTOR_SEED_PROMPT_PATH/red_teaming/{name}.yaml``.
@@ -263,6 +271,7 @@ def with_simulated_conversation(
263271
return cls(
264272
name=name,
265273
attack_class=attack_class,
274+
description=description,
266275
technique_tags=technique_tags,
267276
attack_kwargs=attack_kwargs,
268277
adversarial_chat=adversarial_chat,
@@ -359,6 +368,11 @@ def name(self) -> str:
359368
"""The registry name for this technique."""
360369
return self._name
361370

371+
@property
372+
def description(self) -> str | None:
373+
"""Short human-readable summary of what the technique does, or None."""
374+
return self._description
375+
362376
@property
363377
def technique_tags(self) -> list[str]:
364378
"""Tags controlling which ``ScenarioTechnique`` aggregates include this technique."""

pyrit/setup/initializers/techniques/airt.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
4040
AttackTechniqueFactory(
4141
name="first_letter",
4242
attack_class=PromptSendingAttack,
43+
description="Obfuscates the objective by asking for it encoded as the first letter of each word.",
4344
technique_tags=["single_turn", "airt", "leakage"],
4445
attack_kwargs={
4546
"attack_converter_config": AttackConverterConfig(
@@ -50,6 +51,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
5051
AttackTechniqueFactory(
5152
name="image",
5253
attack_class=PromptSendingAttack,
54+
description="Carries the objective text inside a blank image so it bypasses text-only input handling.",
5355
technique_tags=["single_turn", "airt", "leakage"],
5456
attack_kwargs={
5557
"attack_converter_config": AttackConverterConfig(

pyrit/setup/initializers/techniques/core.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
5353
return [
5454
AttackTechniqueFactory.with_simulated_conversation(
5555
name="role_play_movie_script",
56+
description="Frames the objective as a movie beat by casting the adversarial chat as a screenwriter.",
5657
adversarial_chat_system_prompt_path=(
5758
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_movie_script.yaml"
5859
),
@@ -62,6 +63,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
6263
),
6364
AttackTechniqueFactory.with_simulated_conversation(
6465
name="role_play_video_game",
66+
description="Frames the objective as an in-game mechanic by casting the adversarial chat as a designer.",
6567
adversarial_chat_system_prompt_path=(
6668
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_video_game.yaml"
6769
),
@@ -71,6 +73,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
7173
),
7274
AttackTechniqueFactory.with_simulated_conversation(
7375
name="role_play_trivia_game",
76+
description="Frames the objective as a quiz answer by casting the adversarial chat as a trivia host.",
7477
adversarial_chat_system_prompt_path=(
7578
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_trivia_game.yaml"
7679
),
@@ -80,6 +83,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
8083
),
8184
AttackTechniqueFactory.with_simulated_conversation(
8285
name="role_play_persuasion",
86+
description="Frames a persuasion-resistance test so the target drafts a persuasive spoken script.",
8387
adversarial_chat_system_prompt_path=(
8488
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_persuasion.yaml"
8589
),
@@ -89,6 +93,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
8993
),
9094
AttackTechniqueFactory.with_simulated_conversation(
9195
name="role_play_persuasion_written",
96+
description="Frames a persuasion-resistance test so the target drafts written persuasive material.",
9297
adversarial_chat_system_prompt_path=(
9398
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_persuasion_written.yaml"
9499
),
@@ -99,36 +104,44 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
99104
AttackTechniqueFactory(
100105
name="many_shot",
101106
attack_class=ManyShotJailbreakAttack,
107+
description="Primes the target with many fake example exchanges that model compliance before the ask.",
102108
technique_tags=["multi_turn", "light"],
103109
),
104110
AttackTechniqueFactory(
105111
name="tap",
106112
attack_class=TreeOfAttacksWithPruningAttack,
113+
description="Explores a tree of adversarial prompts, pruning weak branches to refine the attack.",
107114
technique_tags=["multi_turn"],
108115
),
109116
AttackTechniqueFactory.with_simulated_conversation(
110117
name="crescendo_simulated",
118+
description="Escalates gradually over a simulated conversation toward the objective.",
111119
technique_tags=["single_turn"],
112120
),
113121
AttackTechniqueFactory.with_simulated_conversation(
114122
name="crescendo_movie_director",
123+
description="Escalates gradually while posing as a movie director coaxing the target scene by scene.",
115124
technique_tags=["single_turn"],
116125
),
117126
AttackTechniqueFactory.with_simulated_conversation(
118127
name="crescendo_history_lecture",
128+
description="Escalates gradually while framing the ask as an academic history lecture.",
119129
technique_tags=["single_turn"],
120130
),
121131
AttackTechniqueFactory.with_simulated_conversation(
122132
name="crescendo_journalist_interview",
133+
description="Escalates gradually while posing as a journalist drawing the target out.",
123134
technique_tags=["single_turn"],
124135
),
125136
AttackTechniqueFactory(
126137
name="red_teaming",
127138
attack_class=RedTeamingAttack,
139+
description="Uses an adversarial chat model to converse with the target and adapt toward the objective.",
128140
technique_tags=["multi_turn", "light"],
129141
),
130142
AttackTechniqueFactory.with_simulated_conversation(
131143
name="context_compliance",
144+
description="Injects a fabricated prior exchange so the target continues as if it already agreed.",
132145
adversarial_chat_system_prompt_path=EXECUTOR_RED_TEAM_PATH
133146
/ "context_compliance"
134147
/ "context_compliance.yaml",
@@ -140,6 +153,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
140153
AttackTechniqueFactory(
141154
name="flip",
142155
attack_class=PromptSendingAttack,
156+
description="Reverses the objective text so it slips past filters, then asks the target to flip it back.",
143157
technique_tags=["single_turn", "light"],
144158
attack_kwargs={
145159
"attack_converter_config": AttackConverterConfig(

pyrit/setup/initializers/techniques/extra.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
2626
AttackTechniqueFactory(
2727
name="pair",
2828
attack_class=PAIRAttack,
29+
description="Runs the PAIR algorithm, using an adversarial model to iteratively rewrite jailbreak prompts.",
2930
technique_tags=["multi_turn"],
3031
),
3132
AttackTechniqueFactory(
3233
name="violent_durian",
3334
attack_class=RedTeamingAttack,
35+
description="Red-teams with a 'violent durian' persona role-playing a criminal mastermind.",
3436
technique_tags=["multi_turn"],
3537
attack_kwargs={"max_turns": 3},
3638
adversarial_system_prompt=SeedPrompt.from_yaml_file(EXECUTOR_RED_TEAM_PATH / "violent_durian.yaml"),

tests/unit/scenario/core/test_attack_technique_factory.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,35 @@ def test_init_stores_seed_technique(self):
7070

7171
assert factory.seed_technique is seeds
7272

73+
def test_init_description_defaults_to_none(self):
74+
factory = AttackTechniqueFactory(name="test", attack_class=_StubAttack)
75+
76+
assert factory.description is None
77+
78+
def test_init_stores_description(self):
79+
factory = AttackTechniqueFactory(
80+
name="test",
81+
attack_class=_StubAttack,
82+
description="Does the thing.",
83+
)
84+
85+
assert factory.description == "Does the thing."
86+
87+
def test_with_simulated_conversation_forwards_description(self):
88+
factory = AttackTechniqueFactory.with_simulated_conversation(
89+
name="crescendo_journalist_interview",
90+
description="Staged as a journalist interview.",
91+
)
92+
93+
assert factory.description == "Staged as a journalist interview."
94+
95+
def test_description_does_not_affect_identifier(self):
96+
"""Description is decorative metadata and must not change the behavioral identity hash."""
97+
with_desc = AttackTechniqueFactory(name="test", attack_class=_StubAttack, description="Does the thing.")
98+
without_desc = AttackTechniqueFactory(name="test", attack_class=_StubAttack)
99+
100+
assert with_desc.get_identifier().hash == without_desc.get_identifier().hash
101+
73102
def test_validate_kwargs_accepts_valid_params(self):
74103
"""All valid kwarg names should pass without error."""
75104
factory = AttackTechniqueFactory(

0 commit comments

Comments
 (0)