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
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,
next_message_system_prompt_path: str | Path | None = None,
num_turns: int = 3,
Expand All @@ -173,6 +179,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 @@ -224,6 +232,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 @@ -320,6 +329,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
9 changes: 9 additions & 0 deletions pyrit/setup/initializers/techniques/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,43 +46,52 @@ def get_technique_factories() -> list[AttackTechniqueFactory]:
AttackTechniqueFactory(
name="role_play",
attack_class=RolePlayAttack,
description="Frames the objective as a fictional movie script the target treats as creative writing.",
technique_tags=["single_turn", "light"],
attack_kwargs={"role_play_definition_path": RolePlayPaths.MOVIE_SCRIPT.value},
),
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="Crescendo escalation cast as a movie-director persona coaxing the target scene by scene.",
technique_tags=["single_turn"],
),
AttackTechniqueFactory.with_simulated_conversation(
name="crescendo_history_lecture",
description="Crescendo escalation framed as an academic history lecture to normalize the objective.",
technique_tags=["single_turn"],
),
AttackTechniqueFactory.with_simulated_conversation(
name="crescendo_journalist_interview",
description="Crescendo escalation staged as a journalist interview 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(
name="context_compliance",
attack_class=ContextComplianceAttack,
description="Injects a fabricated prior exchange so the target continues as if it already agreed.",
technique_tags=["single_turn", "light"],
),
]
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-teaming 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