Skip to content
Merged
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
14 changes: 14 additions & 0 deletions pyrit/scenario/core/attack_technique_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def __init__(
*,
name: str,
attack_class: type[AttackStrategy[Any, Any]],
description: str | None = None,
technique_tags: list[str] | None = None,
attack_kwargs: dict[str, Any] | None = None,
adversarial_chat: PromptTarget | None = None,
Expand All @@ -90,6 +91,9 @@ def __init__(
name: Registry name for this technique. This is used as the
scenario technique name.
attack_class: The AttackStrategy subclass to instantiate.
description: Short human-readable summary of what the technique does.
Purely descriptive metadata — it does not affect the technique's
behavioral identity.
technique_tags: Tags controlling which ``ScenarioTechnique``
aggregates include this technique (e.g. ``"single_turn"``,
``"multi_turn"``, ``"default"``).
Expand Down Expand Up @@ -129,6 +133,7 @@ class constructor signature and seed-technique shape.
"""
self._name = name
self._attack_class = attack_class
self._description = description
self._technique_tags = list(technique_tags) if technique_tags else []
self._attack_kwargs = dict(attack_kwargs) if attack_kwargs else {}
self._adversarial_chat = adversarial_chat
Expand All @@ -151,6 +156,7 @@ def with_simulated_conversation(
*,
name: str,
attack_class: type[AttackStrategy[Any, Any]] | None = None,
description: str | None = None,
adversarial_chat_system_prompt_path: str | Path | None = None,
simulated_target_system_prompt_path: str | Path | None = None,
next_message_system_prompt_path: str | Path | None = None,
Expand All @@ -175,6 +181,8 @@ def with_simulated_conversation(
``EXECUTOR_SEED_PROMPT_PATH/red_teaming/{name}.yaml``.
attack_class: The AttackStrategy subclass to instantiate. Defaults to
``PromptSendingAttack``.
description: Short human-readable summary of what the technique does.
Forwarded to the factory constructor as descriptive metadata.
adversarial_chat_system_prompt_path: Path to the YAML file containing
the adversarial chat system prompt for the simulated conversation.
Defaults to ``EXECUTOR_SEED_PROMPT_PATH/red_teaming/{name}.yaml``.
Expand Down Expand Up @@ -263,6 +271,7 @@ def with_simulated_conversation(
return cls(
name=name,
attack_class=attack_class,
description=description,
technique_tags=technique_tags,
attack_kwargs=attack_kwargs,
adversarial_chat=adversarial_chat,
Expand Down Expand Up @@ -359,6 +368,11 @@ def name(self) -> str:
"""The registry name for this technique."""
return self._name

@property
def description(self) -> str | None:
"""Short human-readable summary of what the technique does, or None."""
return self._description

@property
def technique_tags(self) -> list[str]:
"""Tags controlling which ``ScenarioTechnique`` aggregates include this technique."""
Expand Down
2 changes: 2 additions & 0 deletions pyrit/setup/initializers/techniques/airt.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
AttackTechniqueFactory(
name="first_letter",
attack_class=PromptSendingAttack,
description="Obfuscates the objective by asking for it encoded as the first letter of each word.",
technique_tags=["single_turn", "airt", "leakage"],
attack_kwargs={
"attack_converter_config": AttackConverterConfig(
Expand All @@ -50,6 +51,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
AttackTechniqueFactory(
name="image",
attack_class=PromptSendingAttack,
description="Carries the objective text inside a blank image so it bypasses text-only input handling.",
technique_tags=["single_turn", "airt", "leakage"],
attack_kwargs={
"attack_converter_config": AttackConverterConfig(
Expand Down
14 changes: 14 additions & 0 deletions pyrit/setup/initializers/techniques/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
return [
AttackTechniqueFactory.with_simulated_conversation(
name="role_play_movie_script",
description="Frames the objective as a movie beat by casting the adversarial chat as a screenwriter.",
adversarial_chat_system_prompt_path=(
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_movie_script.yaml"
),
Expand All @@ -62,6 +63,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
),
AttackTechniqueFactory.with_simulated_conversation(
name="role_play_video_game",
description="Frames the objective as an in-game mechanic by casting the adversarial chat as a designer.",
adversarial_chat_system_prompt_path=(
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_video_game.yaml"
),
Expand All @@ -71,6 +73,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
),
AttackTechniqueFactory.with_simulated_conversation(
name="role_play_trivia_game",
description="Frames the objective as a quiz answer by casting the adversarial chat as a trivia host.",
adversarial_chat_system_prompt_path=(
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_trivia_game.yaml"
),
Expand All @@ -80,6 +83,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
),
AttackTechniqueFactory.with_simulated_conversation(
name="role_play_persuasion",
description="Frames a persuasion-resistance test so the target drafts a persuasive spoken script.",
adversarial_chat_system_prompt_path=(
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_persuasion.yaml"
),
Expand All @@ -89,6 +93,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
),
AttackTechniqueFactory.with_simulated_conversation(
name="role_play_persuasion_written",
description="Frames a persuasion-resistance test so the target drafts written persuasive material.",
adversarial_chat_system_prompt_path=(
EXECUTOR_SEED_PROMPT_PATH / "red_teaming" / "role_play" / "role_play_persuasion_written.yaml"
),
Expand All @@ -99,36 +104,44 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
AttackTechniqueFactory(
name="many_shot",
attack_class=ManyShotJailbreakAttack,
description="Primes the target with many fake example exchanges that model compliance before the ask.",
technique_tags=["multi_turn", "light"],
),
AttackTechniqueFactory(
name="tap",
attack_class=TreeOfAttacksWithPruningAttack,
description="Explores a tree of adversarial prompts, pruning weak branches to refine the attack.",
technique_tags=["multi_turn"],
),
AttackTechniqueFactory.with_simulated_conversation(
name="crescendo_simulated",
description="Escalates gradually over a simulated conversation toward the objective.",
technique_tags=["single_turn"],
),
AttackTechniqueFactory.with_simulated_conversation(
name="crescendo_movie_director",
description="Escalates gradually while posing as a movie director coaxing the target scene by scene.",
technique_tags=["single_turn"],
),
AttackTechniqueFactory.with_simulated_conversation(
name="crescendo_history_lecture",
description="Escalates gradually while framing the ask as an academic history lecture.",
technique_tags=["single_turn"],
),
AttackTechniqueFactory.with_simulated_conversation(
name="crescendo_journalist_interview",
description="Escalates gradually while posing as a journalist drawing the target out.",
technique_tags=["single_turn"],
),
AttackTechniqueFactory(
name="red_teaming",
attack_class=RedTeamingAttack,
description="Uses an adversarial chat model to converse with the target and adapt toward the objective.",
technique_tags=["multi_turn", "light"],
),
AttackTechniqueFactory.with_simulated_conversation(
name="context_compliance",
description="Injects a fabricated prior exchange so the target continues as if it already agreed.",
adversarial_chat_system_prompt_path=EXECUTOR_RED_TEAM_PATH
/ "context_compliance"
/ "context_compliance.yaml",
Expand All @@ -140,6 +153,7 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
AttackTechniqueFactory(
name="flip",
attack_class=PromptSendingAttack,
description="Reverses the objective text so it slips past filters, then asks the target to flip it back.",
technique_tags=["single_turn", "light"],
attack_kwargs={
"attack_converter_config": AttackConverterConfig(
Expand Down
2 changes: 2 additions & 0 deletions pyrit/setup/initializers/techniques/extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
AttackTechniqueFactory(
name="pair",
attack_class=PAIRAttack,
description="Runs the PAIR algorithm, using an adversarial model to iteratively rewrite jailbreak prompts.",
technique_tags=["multi_turn"],
),
AttackTechniqueFactory(
name="violent_durian",
attack_class=RedTeamingAttack,
description="Red-teams with a 'violent durian' persona role-playing a criminal mastermind.",
technique_tags=["multi_turn"],
attack_kwargs={"max_turns": 3},
adversarial_system_prompt=SeedPrompt.from_yaml_file(EXECUTOR_RED_TEAM_PATH / "violent_durian.yaml"),
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/scenario/core/test_attack_technique_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,35 @@ def test_init_stores_seed_technique(self):

assert factory.seed_technique is seeds

def test_init_description_defaults_to_none(self):
factory = AttackTechniqueFactory(name="test", attack_class=_StubAttack)

assert factory.description is None

def test_init_stores_description(self):
factory = AttackTechniqueFactory(
name="test",
attack_class=_StubAttack,
description="Does the thing.",
)

assert factory.description == "Does the thing."

def test_with_simulated_conversation_forwards_description(self):
factory = AttackTechniqueFactory.with_simulated_conversation(
name="crescendo_journalist_interview",
description="Staged as a journalist interview.",
)

assert factory.description == "Staged as a journalist interview."

def test_description_does_not_affect_identifier(self):
"""Description is decorative metadata and must not change the behavioral identity hash."""
with_desc = AttackTechniqueFactory(name="test", attack_class=_StubAttack, description="Does the thing.")
without_desc = AttackTechniqueFactory(name="test", attack_class=_StubAttack)

assert with_desc.get_identifier().hash == without_desc.get_identifier().hash

def test_validate_kwargs_accepts_valid_params(self):
"""All valid kwarg names should pass without error."""
factory = AttackTechniqueFactory(
Expand Down
Loading