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
8 changes: 7 additions & 1 deletion pyrit/converter/prompt_template_converter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

import hashlib
import re

from pyrit.converter.converter import Converter, ConverterResult
Expand All @@ -16,6 +17,10 @@ class PromptTemplateConverter(Converter):
framing (e.g. ``TASK is '{{ prompt }}'``) as well as wrapping the prompt in
surrounding content, such as hiding it in an HTML comment for indirect prompt
injection (e.g. ``<p>Visible text</p><!-- {{ prompt }} -->``).

The converter identifier stores a fixed-size hash of the configured template,
rather than the template text. The deprecated ``TaskFramingConverter`` retains
its legacy identifier.
"""

SUPPORTED_INPUT_TYPES = ("text",)
Expand Down Expand Up @@ -56,9 +61,10 @@ def _build_identifier(self) -> ComponentIdentifier:
Returns:
ComponentIdentifier: The identifier for this converter.
"""
template_hash = hashlib.sha256(self._template.encode("utf-8")).hexdigest()[:16]
return self._create_identifier(
params={
"template": self._template,
"template_hash": template_hash,
"strip_characters": self._strip_characters,
},
)
Expand Down
29 changes: 27 additions & 2 deletions tests/unit/converter/test_prompt_template_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,24 @@ def test_input_output_types():
assert converter.output_supported("text") is True


def test_identifier_includes_template_and_strip_characters():
def test_identifier_includes_template_hash_and_strip_characters():
converter = PromptTemplateConverter(template="T: {{ prompt }}", strip_characters="'")
identifier = converter.get_identifier()
assert identifier.class_name == "PromptTemplateConverter"
assert identifier.params["template"] == "T: {{ prompt }}"
assert identifier.params["template_hash"] == "1d6417505e0571d2"
assert identifier.params["strip_characters"] == "'"
assert "template" not in identifier.params


def test_identifier_keeps_hash():
converter = PromptTemplateConverter(template="T: {{ prompt }}", strip_characters="'")
assert converter.get_identifier().hash == "f7a37b712954fc0dd196ee9481ca7a1e0779841b768171bb6af146a046dde693"


def test_identifier_matches_for_equivalent_templates():
first = PromptTemplateConverter(template="A {{ prompt }}").get_identifier()
second = PromptTemplateConverter(template="A {{ prompt }}").get_identifier()
assert first == second


def test_identifier_differs_by_template():
Expand All @@ -110,6 +122,19 @@ def test_identifier_differs_by_template():
assert first.hash != second.hash


def test_identifier_differs_by_strip_characters():
first = PromptTemplateConverter(template="A {{ prompt }}", strip_characters="'").get_identifier()
second = PromptTemplateConverter(template="A {{ prompt }}", strip_characters='"').get_identifier()
assert first.hash != second.hash


def test_long_template_identifier_uses_fixed_size_hash():
template = f"{'A' * 10_000} {{{{ prompt }}}}"
identifier = PromptTemplateConverter(template=template).get_identifier()
assert identifier.params["template_hash"] == "a280c02c55a808af"
assert template not in identifier.model_dump_json()


# Markdown hidden-text recipes from the converters doc: put the prompt on one line,
# backslash-escape ``\`` and the delimiter, then insert it with PromptTemplateConverter.
_MARKDOWN_RECIPES = {
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/converter/test_task_framing_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,10 @@ def test_identifier_keeps_task_template_param_name():
assert identifier.params["task_template"] == "TASK is '{{ prompt }}'"
assert identifier.params["strip_characters"] == "'"
assert "template" not in identifier.params
assert "template_hash" not in identifier.params


def test_identifier_keeps_legacy_hash():
# Pinned to the v1.1.0 value so the deprecated converter keeps matching identifiers already stored.
identifier = _make(strip_characters="'").get_identifier()
assert identifier.hash == "b680cca8b216deec74b66758dd6bdc005fe21e593fbd0a9a975fe40e7e9fe56d"
Loading