From ba107b5f6438c65a83ac8d196bcf3ab543a40a4a Mon Sep 17 00:00:00 2001 From: Paulina Kalicka <71526180+paulinek13@users.noreply.github.com> Date: Wed, 7 Jan 2026 21:52:45 +0100 Subject: [PATCH 1/3] some fixes --- pyproject.toml | 2 +- pyrit/prompt_converter/base2048_converter.py | 2 +- pyrit/prompt_converter/base64_converter.py | 2 +- .../prompt_converter/character_space_converter.py | 14 +++++++++++++- .../prompt_converter/charswap_attack_converter.py | 5 ++++- pyrit/prompt_converter/denylist_converter.py | 4 ++-- pyrit/prompt_converter/flip_converter.py | 14 +++++++++++++- pyrit/prompt_converter/zero_width_converter.py | 2 +- 8 files changed, 36 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 55947bc8f1..cbd3069db9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -258,7 +258,7 @@ extend-select = [ # Temporary ignores for pyrit/ subdirectories until issue #1176 # https://github.com/Azure/PyRIT/issues/1176 is fully resolved # TODO: Remove these ignores once the issues are fixed -"pyrit/{auxiliary_attacks,exceptions,models,prompt_converter,ui}/**/*.py" = ["D101", "D102", "D103", "D104", "D105", "D106", "D107", "D401", "D404", "D417", "D418", "DOC102", "DOC201", "DOC202", "DOC402", "DOC501"] +"pyrit/{auxiliary_attacks,exceptions,models,ui}/**/*.py" = ["D101", "D102", "D103", "D104", "D105", "D106", "D107", "D401", "D404", "D417", "D418", "DOC102", "DOC201", "DOC202", "DOC402", "DOC501"] "pyrit/__init__.py" = ["D104"] [tool.ruff.lint.pydocstyle] diff --git a/pyrit/prompt_converter/base2048_converter.py b/pyrit/prompt_converter/base2048_converter.py index d7bc87dcc2..1d6605e612 100644 --- a/pyrit/prompt_converter/base2048_converter.py +++ b/pyrit/prompt_converter/base2048_converter.py @@ -30,7 +30,7 @@ def __init__(self) -> None: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt to base2048 encoding. + Convert the given prompt to base2048 encoding. Args: prompt: The prompt to be converted. diff --git a/pyrit/prompt_converter/base64_converter.py b/pyrit/prompt_converter/base64_converter.py index a38285afe1..4e87885297 100644 --- a/pyrit/prompt_converter/base64_converter.py +++ b/pyrit/prompt_converter/base64_converter.py @@ -46,7 +46,7 @@ def __init__(self, *, encoding_func: EncodingFunc = "b64encode") -> None: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt to base64 encoding. + Convert the given prompt to base64 encoding. Args: prompt: The prompt to be converted. diff --git a/pyrit/prompt_converter/character_space_converter.py b/pyrit/prompt_converter/character_space_converter.py index fe80ee991c..49ba098118 100644 --- a/pyrit/prompt_converter/character_space_converter.py +++ b/pyrit/prompt_converter/character_space_converter.py @@ -19,7 +19,19 @@ class CharacterSpaceConverter(PromptConverter): SUPPORTED_OUTPUT_TYPES = ("text",) async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: - """Converts the given prompt by removing punctuation and spacing out characters.""" + """ + Convert the given prompt by removing punctuation and spacing out characters. + + Args: + prompt (str): The input text prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the converted text. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Input type not supported") converted_text = re.sub("[!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~]", "", " ".join(prompt)) diff --git a/pyrit/prompt_converter/charswap_attack_converter.py b/pyrit/prompt_converter/charswap_attack_converter.py index ae5badcefc..b462bfc8eb 100644 --- a/pyrit/prompt_converter/charswap_attack_converter.py +++ b/pyrit/prompt_converter/charswap_attack_converter.py @@ -24,7 +24,7 @@ def __init__( word_selection_strategy: Optional[WordSelectionStrategy] = None, ): """ - Initializes the converter with the specified parameters. + Initialize the converter with the specified parameters. By default, 20% of randomly selected words will be perturbed. @@ -33,6 +33,9 @@ def __init__( The higher the number the higher the chance that words are different from the original prompt. word_selection_strategy (Optional[WordSelectionStrategy]): Strategy for selecting which words to convert. If None, defaults to WordProportionSelectionStrategy(proportion=0.2). + + Raises: + ValueError: If max_iterations is not greater than 0. """ # Default to 20% proportion if no strategy provided if word_selection_strategy is None: diff --git a/pyrit/prompt_converter/denylist_converter.py b/pyrit/prompt_converter/denylist_converter.py index 031a40c6b6..bf3af32bc8 100644 --- a/pyrit/prompt_converter/denylist_converter.py +++ b/pyrit/prompt_converter/denylist_converter.py @@ -30,7 +30,7 @@ def __init__( denylist: list[str] = [], ): """ - Initializes the converter with a target, an optional system prompt template, and a denylist. + Initialize the converter with a target, an optional system prompt template, and a denylist. Args: converter_target (PromptChatTarget): The target for the prompt conversion. @@ -52,7 +52,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by removing any words or phrases that are in the denylist, + Convert the given prompt by removing any words or phrases that are in the denylist, replacing them with synonymous words. Args: diff --git a/pyrit/prompt_converter/flip_converter.py b/pyrit/prompt_converter/flip_converter.py index 5547a9d76d..9afe0acbe7 100644 --- a/pyrit/prompt_converter/flip_converter.py +++ b/pyrit/prompt_converter/flip_converter.py @@ -14,7 +14,19 @@ class FlipConverter(PromptConverter): SUPPORTED_OUTPUT_TYPES = ("text",) async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: - """Converts the given prompt by reversing the text.""" + """ + Convert the given prompt by reversing the text. + + Args: + prompt: The prompt to be converted. + input_type: Type of data. + + Returns: + The converted text representation of the original prompt with characters reversed. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/zero_width_converter.py b/pyrit/prompt_converter/zero_width_converter.py index a29bdfa3cc..7b380c67fc 100644 --- a/pyrit/prompt_converter/zero_width_converter.py +++ b/pyrit/prompt_converter/zero_width_converter.py @@ -18,7 +18,7 @@ class ZeroWidthConverter(PromptConverter): async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by injecting zero-width spaces between each character. + Convert the given prompt by injecting zero-width spaces between each character. Args: prompt (str): The prompt to be converted. From e2cf5bce238a63f2d6f513f890d013ef569be1fd Mon Sep 17 00:00:00 2001 From: Paulina Kalicka <71526180+paulinek13@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:26:05 +0100 Subject: [PATCH 2/3] fixes --- pyrit/prompt_converter/__init__.py | 10 +++ .../add_image_text_converter.py | 11 +-- .../add_image_to_video_converter.py | 10 ++- .../add_text_image_converter.py | 8 +-- .../ansi_escape/ansi_attack_converter.py | 34 ++++++++- pyrit/prompt_converter/ascii_art_converter.py | 16 ++++- .../ask_to_decode_converter.py | 6 +- pyrit/prompt_converter/atbash_converter.py | 16 ++++- .../audio_frequency_converter.py | 4 +- .../azure_speech_audio_to_text_converter.py | 16 +++-- .../azure_speech_text_to_audio_converter.py | 5 +- pyrit/prompt_converter/bin_ascii_converter.py | 6 ++ pyrit/prompt_converter/binary_converter.py | 35 +++++++-- pyrit/prompt_converter/braille_converter.py | 10 ++- pyrit/prompt_converter/caesar_converter.py | 16 ++++- .../charswap_attack_converter.py | 9 +++ .../codechameleon_converter.py | 38 ++++++++-- .../colloquial_wordswap_converter.py | 16 ++++- pyrit/prompt_converter/diacritic_converter.py | 26 +++++-- pyrit/prompt_converter/emoji_converter.py | 9 +++ .../first_letter_converter.py | 20 +++++- .../human_in_the_loop_converter.py | 4 +- .../image_compression_converter.py | 53 ++++++++++++-- .../insert_punctuation_converter.py | 6 +- pyrit/prompt_converter/leetspeak_converter.py | 11 ++- .../llm_generic_text_converter.py | 7 +- .../malicious_question_generator_converter.py | 30 +++++++- .../prompt_converter/math_prompt_converter.py | 4 +- pyrit/prompt_converter/morse_converter.py | 14 +++- pyrit/prompt_converter/nato_converter.py | 4 +- .../negation_trap_converter.py | 26 ++++++- pyrit/prompt_converter/noise_converter.py | 2 +- pyrit/prompt_converter/pdf_converter.py | 30 +++++--- .../prompt_converter/persuasion_converter.py | 29 ++++++-- pyrit/prompt_converter/prompt_converter.py | 22 +++--- pyrit/prompt_converter/qr_code_converter.py | 4 +- .../random_capital_letters_converter.py | 58 ++++++++++++--- .../random_translation_converter.py | 6 +- .../repeat_token_converter.py | 14 +++- pyrit/prompt_converter/rot13_converter.py | 9 +++ .../search_replace_converter.py | 4 +- .../selective_text_converter.py | 26 +++++-- .../prompt_converter/string_join_converter.py | 11 ++- .../suffix_append_converter.py | 22 ++++++ .../prompt_converter/superscript_converter.py | 9 +++ .../template_segment_converter.py | 6 +- pyrit/prompt_converter/tense_converter.py | 4 +- .../text_jailbreak_converter.py | 14 +++- .../text_selection_strategy.py | 54 +++++++------- .../token_smuggling/__init__.py | 5 ++ .../ascii_smuggler_converter.py | 6 +- .../prompt_converter/token_smuggling/base.py | 29 ++++++-- .../sneaky_bits_smuggler_converter.py | 6 +- .../variation_selector_smuggler_converter.py | 22 ++++-- pyrit/prompt_converter/tone_converter.py | 2 +- .../toxic_sentence_generator_converter.py | 22 +++++- .../prompt_converter/translation_converter.py | 5 +- .../transparency_attack_converter.py | 72 ++++++++++++++++--- .../unicode_confusable_converter.py | 13 ++-- .../unicode_replacement_converter.py | 20 +++++- .../prompt_converter/unicode_sub_converter.py | 18 ++++- pyrit/prompt_converter/url_converter.py | 12 +++- pyrit/prompt_converter/variation_converter.py | 20 ++++-- .../prompt_converter/word_level_converter.py | 18 +++-- pyrit/prompt_converter/zalgo_converter.py | 12 +++- 65 files changed, 885 insertions(+), 201 deletions(-) diff --git a/pyrit/prompt_converter/__init__.py b/pyrit/prompt_converter/__init__.py index ecb5408ad3..04ef131f70 100644 --- a/pyrit/prompt_converter/__init__.py +++ b/pyrit/prompt_converter/__init__.py @@ -1,6 +1,16 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +""" +Prompt converters for transforming prompts before sending them to targets in red teaming workflows. + +Converters are organized into categories: Text-to-Text (encoding, obfuscation, translation, variation), +Audio (text-to-audio, audio-to-text, audio-to-audio), Image (text-to-image, image-to-image), +Video (image-to-video), File (text-to-PDF/URL), Selective Converting (partial prompt transformation), +and Human-in-the-Loop (interactive review). Converters can be stacked together to create complex +transformation pipelines for testing AI system robustness. +""" + from pyrit.prompt_converter.add_image_text_converter import AddImageTextConverter from pyrit.prompt_converter.add_image_to_video_converter import AddImageVideoConverter from pyrit.prompt_converter.add_text_image_converter import AddTextImageConverter diff --git a/pyrit/prompt_converter/add_image_text_converter.py b/pyrit/prompt_converter/add_image_text_converter.py index 61a19581d9..4d4fc5cdc4 100644 --- a/pyrit/prompt_converter/add_image_text_converter.py +++ b/pyrit/prompt_converter/add_image_text_converter.py @@ -36,7 +36,7 @@ def __init__( y_pos: int = 10, ): """ - Initializes the converter with the image file path and text properties. + Initialize the converter with the image file path and text properties. Args: img_to_add (str): File path of image to add text to. @@ -63,7 +63,7 @@ def __init__( def _load_font(self): """ - Loads the font for a given font name and font size. + Load the font for a given font name and font size. Returns: ImageFont.FreeTypeFont or ImageFont.ImageFont: The loaded font object. If the specified font @@ -82,13 +82,16 @@ def _load_font(self): def _add_text_to_image(self, text: str) -> Image.Image: """ - Adds wrapped text to the image at `self._img_to_add`. + Add wrapped text to the image at `self._img_to_add`. Args: text (str): The text to add to the image. Returns: Image.Image: The image with added text. + + Raises: + ValueError: If ``text`` is empty. """ if not text: raise ValueError("Please provide valid text value") @@ -121,7 +124,7 @@ def _add_text_to_image(self, text: str) -> Image.Image: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by adding it as text to the image. + Convert the given prompt by adding it as text to the image. Args: prompt (str): The text to be added to the image. diff --git a/pyrit/prompt_converter/add_image_to_video_converter.py b/pyrit/prompt_converter/add_image_to_video_converter.py index 9ed0cd54fd..9893c83bbe 100644 --- a/pyrit/prompt_converter/add_image_to_video_converter.py +++ b/pyrit/prompt_converter/add_image_to_video_converter.py @@ -42,7 +42,7 @@ def __init__( img_resize_size: tuple = (500, 500), ): """ - Initializes the converter with the video path and image properties. + Initialize the converter with the video path and image properties. Args: video_path (str): File path of video to add image to. @@ -63,7 +63,7 @@ def __init__( async def _add_image_to_video(self, image_path: str, output_path: str) -> str: """ - Adds an image to video. + Add an image to video. Args: image_path (str): The image path to add to video. @@ -71,6 +71,10 @@ async def _add_image_to_video(self, image_path: str, output_path: str) -> str: Returns: str: The output video path. + + Raises: + ModuleNotFoundError: If OpenCV is not installed. + ValueError: If the image path is invalid or unsupported video format. """ try: import cv2 # noqa: F401 @@ -167,7 +171,7 @@ async def _add_image_to_video(self, image_path: str, output_path: str) -> str: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "image_path") -> ConverterResult: """ - Converts the given prompt (image) by adding it to a video. + Convert the given prompt (image) by adding it to a video. Args: prompt (str): The image path to be added to the video. diff --git a/pyrit/prompt_converter/add_text_image_converter.py b/pyrit/prompt_converter/add_text_image_converter.py index 0c4186ac5d..b5ec5119d1 100644 --- a/pyrit/prompt_converter/add_text_image_converter.py +++ b/pyrit/prompt_converter/add_text_image_converter.py @@ -36,7 +36,7 @@ def __init__( y_pos: int = 10, ): """ - Initializes the converter with the text and text properties. + Initialize the converter with the text and text properties. Args: text_to_add (str): Text to add to an image. Defaults to empty string. @@ -63,7 +63,7 @@ def __init__( def _load_font(self): """ - Loads the font for a given font name and font size. + Load the font for a given font name and font size. Returns: ImageFont.FreeTypeFont or ImageFont.ImageFont: The loaded font object. If the specified font @@ -82,7 +82,7 @@ def _load_font(self): def _add_text_to_image(self, image: Image.Image) -> Image.Image: """ - Adds wrapped text to the image. + Add wrapped text to the image. Args: image (Image.Image): The image to which text will be added. @@ -117,7 +117,7 @@ def _add_text_to_image(self, image: Image.Image) -> Image.Image: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "image_path") -> ConverterResult: """ - Converts the given prompt (image) by adding text to it. + Convert the given prompt (image) by adding text to it. Args: prompt (str): The image file path to which text will be added. diff --git a/pyrit/prompt_converter/ansi_escape/ansi_attack_converter.py b/pyrit/prompt_converter/ansi_escape/ansi_attack_converter.py index 7951d46e7f..bdd80dd6ca 100644 --- a/pyrit/prompt_converter/ansi_escape/ansi_attack_converter.py +++ b/pyrit/prompt_converter/ansi_escape/ansi_attack_converter.py @@ -41,7 +41,7 @@ def __init__( incorporate_user_prompt: bool = True, ): """ - Initializes the converter with various options to control the scenarios generated. + Initialize the converter with various options to control the scenarios generated. Args: include_raw (bool): Include scenarios with raw ANSI codes. @@ -59,13 +59,43 @@ def __init__( self.incorporate_user_prompt = incorporate_user_prompt def input_supported(self, input_type: PromptDataType) -> bool: + """ + Check if the input type is supported. + + Args: + input_type (PromptDataType): The type of input data. + + Returns: + bool: True if the input type is supported, False otherwise. + """ return input_type == "text" def output_supported(self, output_type: PromptDataType) -> bool: + """ + Check if the output type is supported. + + Args: + output_type (PromptDataType): The type of output data. + + Returns: + bool: True if the output type is supported, False otherwise. + """ return output_type == "text" async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: - """Converts the given prompt into an ANSI attack scenario.""" + """ + Convert the given prompt into an ANSI attack scenario. + + Args: + prompt (str): The original user prompt. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the generated ANSI scenario prompt. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/ascii_art_converter.py b/pyrit/prompt_converter/ascii_art_converter.py index e180dec570..5a1202f079 100644 --- a/pyrit/prompt_converter/ascii_art_converter.py +++ b/pyrit/prompt_converter/ascii_art_converter.py @@ -17,7 +17,7 @@ class AsciiArtConverter(PromptConverter): def __init__(self, font="rand"): """ - Initializes the converter with a specified font. + Initialize the converter with a specified font. Args: font (str): The font to use for ASCII art. Defaults to "rand" which selects a random font. @@ -25,7 +25,19 @@ def __init__(self, font="rand"): self.font_value = font async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: - """Converts the given prompt into ASCII art.""" + """ + Convert the given prompt into ASCII art. + + Args: + prompt (str): The prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the ASCII art representation of the prompt. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/ask_to_decode_converter.py b/pyrit/prompt_converter/ask_to_decode_converter.py index 4e8ea72fc2..d08b7dde6e 100644 --- a/pyrit/prompt_converter/ask_to_decode_converter.py +++ b/pyrit/prompt_converter/ask_to_decode_converter.py @@ -40,7 +40,7 @@ class AskToDecodeConverter(PromptConverter): def __init__(self, template=None, encoding_name: str = "cipher") -> None: """ - Initializes the converter with a specified encoding name and template. + Initialize the converter with a specified encoding name and template. By default, if no template is provided, a random template from basic_templates will be used. If an encoding_name is provided, both basic_templates and @@ -59,14 +59,14 @@ def __init__(self, template=None, encoding_name: str = "cipher") -> None: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given encoded text by wrapping it with a decoding request prompt. + Convert the given encoded text by wrapping it with a decoding request prompt. Args: prompt (str): The encoded text to be wrapped with a decoding request. input_type (PromptDataType, optional): Type of input data. Defaults to "text". Returns: - ConverterResult: The encoded text wrapped in a decoding prompt. + ConverterResult: The result containing the converted prompt. Raises: ValueError: If the input type is not supported (only "text" is supported). diff --git a/pyrit/prompt_converter/atbash_converter.py b/pyrit/prompt_converter/atbash_converter.py index 7df996ca7e..d8ee91ba8d 100644 --- a/pyrit/prompt_converter/atbash_converter.py +++ b/pyrit/prompt_converter/atbash_converter.py @@ -25,7 +25,7 @@ class AtbashConverter(PromptConverter): def __init__(self, *, append_description: bool = False) -> None: """ - Initializes the converter with an option to append a description. + Initialize the converter with an option to append a description. Args: append_description (bool): If True, appends plaintext "expert" text to the prompt. @@ -40,7 +40,19 @@ def __init__(self, *, append_description: bool = False) -> None: ) async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: - """Converts the given prompt using the Atbash cipher.""" + """ + Convert the given prompt using the Atbash cipher. + + Args: + prompt (str): The prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the encoded prompt. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/audio_frequency_converter.py b/pyrit/prompt_converter/audio_frequency_converter.py index 9ba3d26990..33153a13d4 100644 --- a/pyrit/prompt_converter/audio_frequency_converter.py +++ b/pyrit/prompt_converter/audio_frequency_converter.py @@ -33,7 +33,7 @@ def __init__( shift_value: int = 20000, ) -> None: """ - Initializes the converter with the specified output format and shift value. + Initialize the converter with the specified output format and shift value. Args: output_format (str): The format of the audio file, defaults to "wav". @@ -44,7 +44,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "audio_path") -> ConverterResult: """ - Converts the given audio file by shifting its frequency. + Convert the given audio file by shifting its frequency. Args: prompt (str): File path to the audio file to be converted. diff --git a/pyrit/prompt_converter/azure_speech_audio_to_text_converter.py b/pyrit/prompt_converter/azure_speech_audio_to_text_converter.py index 3245cdcd0c..77352e5893 100644 --- a/pyrit/prompt_converter/azure_speech_audio_to_text_converter.py +++ b/pyrit/prompt_converter/azure_speech_audio_to_text_converter.py @@ -42,7 +42,7 @@ def __init__( recognition_language: str = "en-US", ) -> None: """ - Initializes the converter with Azure Speech service credentials and recognition language. + Initialize the converter with Azure Speech service credentials and recognition language. Args: azure_speech_region (str, Optional): The name of the Azure region. @@ -88,7 +88,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "audio_path") -> ConverterResult: """ - Converts the given audio file into its text representation. + Convert the given audio file into its text representation. Args: prompt (str): File path to the audio file to be transcribed. @@ -120,13 +120,16 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "audi def recognize_audio(self, audio_bytes: bytes) -> str: """ - Recognizes audio file and returns transcribed text. + Recognize audio file and return transcribed text. Args: audio_bytes (bytes): Audio bytes input. Returns: str: Transcribed text. + + Raises: + ModuleNotFoundError: If the azure.cognitiveservices.speech module is not installed. """ try: import azure.cognitiveservices.speech as speechsdk # noqa: F811 @@ -177,7 +180,7 @@ def recognize_audio(self, audio_bytes: bytes) -> str: def transcript_cb(self, evt: Any, transcript: list[str]) -> None: """ - Callback function that appends transcribed text upon receiving a "recognized" event. + Append transcribed text upon receiving a "recognized" event. Args: evt (speechsdk.SpeechRecognitionEventArgs): Event. @@ -188,11 +191,14 @@ def transcript_cb(self, evt: Any, transcript: list[str]) -> None: def stop_cb(self, evt: Any, recognizer: Any) -> None: """ - Callback function that stops continuous recognition upon receiving an event 'evt'. + Stop continuous recognition upon receiving an event 'evt'. Args: evt (speechsdk.SpeechRecognitionEventArgs): Event. recognizer (speechsdk.SpeechRecognizer): Speech recognizer object. + + Raises: + ModuleNotFoundError: If the azure.cognitiveservices.speech module is not installed. """ try: import azure.cognitiveservices.speech as speechsdk # noqa: F811 diff --git a/pyrit/prompt_converter/azure_speech_text_to_audio_converter.py b/pyrit/prompt_converter/azure_speech_text_to_audio_converter.py index 28e216f1e3..87db4b71d7 100644 --- a/pyrit/prompt_converter/azure_speech_text_to_audio_converter.py +++ b/pyrit/prompt_converter/azure_speech_text_to_audio_converter.py @@ -46,7 +46,7 @@ def __init__( output_format: AzureSpeechAudioFormat = "wav", ) -> None: """ - Initializes the converter with Azure Speech service credentials, synthesis language, and voice name. + Initialize the converter with Azure Speech service credentials, synthesis language, and voice name. Args: azure_speech_region (str, Optional): The name of the Azure region. @@ -61,7 +61,6 @@ def __init__( synthesis_voice_name (str): Synthesis voice name, see URL. For more details see the following link for synthesis language and synthesis voice: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support - filename (str): File name to be generated. Please include either .wav or .mp3. output_format (str): Either wav or mp3. Must match the file prefix. Raises: @@ -96,7 +95,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given text prompt into its audio representation. + Convert the given text prompt into its audio representation. Args: prompt (str): The text prompt to be converted into audio. diff --git a/pyrit/prompt_converter/bin_ascii_converter.py b/pyrit/prompt_converter/bin_ascii_converter.py index 919f2ec915..84c71f291e 100644 --- a/pyrit/prompt_converter/bin_ascii_converter.py +++ b/pyrit/prompt_converter/bin_ascii_converter.py @@ -41,6 +41,9 @@ def __init__( If None, all words will be converted. word_split_separator (Optional[str]): Separator used to split words in the input text. Defaults to " ". + + Raises: + ValueError: If an invalid ``encoding_func`` is provided. """ super().__init__( word_selection_strategy=word_selection_strategy, @@ -63,6 +66,9 @@ async def convert_word_async(self, word: str) -> str: Returns: str: The encoded word. + + Raises: + ValueError: If an unsupported encoding function is encountered. """ if self._encoding_func == "hex": return word.encode("utf-8").hex().upper() diff --git a/pyrit/prompt_converter/binary_converter.py b/pyrit/prompt_converter/binary_converter.py index 5a7e58f8f6..61305c6b2f 100644 --- a/pyrit/prompt_converter/binary_converter.py +++ b/pyrit/prompt_converter/binary_converter.py @@ -29,13 +29,16 @@ def __init__( word_selection_strategy: Optional[WordSelectionStrategy] = None, ): """ - Initializes the converter with the specified bits per character and selection strategy. + Initialize the converter with the specified bits per character and selection strategy. Args: bits_per_char (BinaryConverter.BitsPerChar): Number of bits to use for each character (8, 16, or 32). Default is 16 bits. word_selection_strategy (Optional[WordSelectionStrategy]): Strategy for selecting which words to convert. If None, all words will be converted. + + Raises: + TypeError: If ``bits_per_char`` is not an instance of BinaryConverter.BitsPerChar Enum. """ super().__init__(word_selection_strategy=word_selection_strategy) @@ -44,7 +47,15 @@ def __init__( self.bits_per_char = bits_per_char def validate_input(self, prompt): - """Checks if ``bits_per_char`` is sufficient for the characters in the prompt.""" + """ + Check if ``bits_per_char`` is sufficient for the characters in the prompt. + + Args: + prompt (str): The input text prompt to validate. + + Raises: + ValueError: If ``bits_per_char`` is too small to represent any character in the prompt. + """ bits = self.bits_per_char.value max_code_point = max((ord(char) for char in prompt), default=0) min_bits_required = max_code_point.bit_length() @@ -55,11 +66,27 @@ def validate_input(self, prompt): ) async def convert_word_async(self, word: str) -> str: - """Converts each character in the word to its binary representation.""" + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ bits = self.bits_per_char.value return " ".join(format(ord(char), f"0{bits}b") for char in word) def join_words(self, words: list[str]) -> str: - """Joins the converted words with the binary representation of a space.""" + """ + Join the converted words with the binary representation of a space. + + Args: + words (list[str]): The list of converted words. + + Returns: + str: The final joined string with spaces in binary format. + """ space_binary = format(ord(" "), f"0{self.bits_per_char.value}b") return f" {space_binary} ".join(words) diff --git a/pyrit/prompt_converter/braille_converter.py b/pyrit/prompt_converter/braille_converter.py index 667f0bbd73..d277db7d62 100644 --- a/pyrit/prompt_converter/braille_converter.py +++ b/pyrit/prompt_converter/braille_converter.py @@ -27,7 +27,7 @@ class BrailleConverter(PromptConverter): async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given text into Braille Unicode representation. + Convert the given text into Braille Unicode representation. Args: prompt (str): The text to be converted to Braille. @@ -48,9 +48,15 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text def _get_braile(self, text) -> str: """ - This retrieves the braille representation of the input text. + Retrieve the braille representation of the input text. Taken from Garak https://github.com/NVIDIA/garak/blob/main/garak/probes/encoding.py + + Args: + text (str): The input text to be converted to braille. + + Returns: + str: The braille representation of the input text. """ characterUnicodes = { "a": "\u2801", diff --git a/pyrit/prompt_converter/caesar_converter.py b/pyrit/prompt_converter/caesar_converter.py index b55a4aa56b..b28b0ad3ed 100644 --- a/pyrit/prompt_converter/caesar_converter.py +++ b/pyrit/prompt_converter/caesar_converter.py @@ -23,7 +23,7 @@ class CaesarConverter(PromptConverter): def __init__(self, *, caesar_offset: int, append_description: bool = False) -> None: """ - Initializes the converter with a Caesar cipher offset and an option to append a description. + Initialize the converter with a Caesar cipher offset and an option to append a description. Args: caesar_offset (int): Offset for caesar cipher, range 0 to 25 (inclusive). @@ -46,7 +46,19 @@ def __init__(self, *, caesar_offset: int, append_description: bool = False) -> N ) async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: - """Converts the given prompt using the Caesar cipher.""" + """ + Convert the given prompt using the Caesar cipher. + + Args: + prompt (str): The input prompt to be converted. + input_type (PromptDataType): The type of the input prompt. Must be "text". + + Returns: + ConverterResult: The result containing the converted prompt and its type. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/charswap_attack_converter.py b/pyrit/prompt_converter/charswap_attack_converter.py index b462bfc8eb..1202f4493b 100644 --- a/pyrit/prompt_converter/charswap_attack_converter.py +++ b/pyrit/prompt_converter/charswap_attack_converter.py @@ -50,6 +50,15 @@ def __init__( self.max_iterations = max_iterations async def convert_word_async(self, word: str) -> str: + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ return self._perturb_word(word) def _perturb_word(self, word: str) -> str: diff --git a/pyrit/prompt_converter/codechameleon_converter.py b/pyrit/prompt_converter/codechameleon_converter.py index 229a89ac07..b1227799d9 100644 --- a/pyrit/prompt_converter/codechameleon_converter.py +++ b/pyrit/prompt_converter/codechameleon_converter.py @@ -54,7 +54,7 @@ def __init__( decrypt_function: Optional[Callable | list[Callable | str]] = None, ) -> None: """ - Initializes the converter with the specified encryption type and optional functions. + Initialize the converter with the specified encryption type and optional functions. Args: encrypt_type (str): Must be one of "custom", "reverse", "binary_tree", "odd_even" or "length". @@ -98,7 +98,19 @@ def __init__( ) async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: - """Converts the given prompt by applying the specified encryption function.""" + """ + Convert the given prompt by applying the specified encryption function. + + Args: + prompt (str): The input prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the converted prompt. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Input type not supported") @@ -141,7 +153,17 @@ def __init__(self, value): self.right = None def build_tree(words, start, end): - """Builds the binary tree from the list of words.""" + """ + Recursively build a balanced binary tree from a sublist of words. + + Args: + words (list): List of words to build the tree from. + start (int): Starting index (inclusive) of the current sublist. + end (int): Ending index (inclusive) of the current sublist. + + Returns: + TreeNode or None: The root node of the subtree, or None if start > end. + """ if start > end: return None @@ -154,7 +176,15 @@ def build_tree(words, start, end): return node def tree_to_json(node): - """Converts a tree to a JSON representation.""" + """ + Convert a tree to a JSON representation. + + Args: + node (TreeNode): The root node of the tree. + + Returns: + dict or None: JSON representation of the tree, or None if the node is None. + """ if node is None: return None return {"value": node.value, "left": tree_to_json(node.left), "right": tree_to_json(node.right)} diff --git a/pyrit/prompt_converter/colloquial_wordswap_converter.py b/pyrit/prompt_converter/colloquial_wordswap_converter.py index 77d95303a8..7fd5c842dc 100644 --- a/pyrit/prompt_converter/colloquial_wordswap_converter.py +++ b/pyrit/prompt_converter/colloquial_wordswap_converter.py @@ -21,7 +21,7 @@ def __init__( self, deterministic: bool = False, custom_substitutions: Optional[Dict[str, List[str]]] = None ) -> None: """ - Initializes the converter with optional deterministic mode and custom substitutions. + Initialize the converter with optional deterministic mode and custom substitutions. Args: deterministic (bool): If True, use the first substitution for each wordswap. @@ -52,7 +52,19 @@ def __init__( self._deterministic = deterministic async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: - """Converts the given prompt by replacing words with colloquial Singaporean terms.""" + """ + Convert the given prompt by replacing words with colloquial Singaporean terms. + + Args: + prompt (str): The input text prompt to be converted. + input_type (PromptDataType): The type of the input prompt. Defaults to "text". + + Returns: + ConverterResult: The result containing the converted prompt. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/diacritic_converter.py b/pyrit/prompt_converter/diacritic_converter.py index cb9d4d7719..64ebaa753e 100644 --- a/pyrit/prompt_converter/diacritic_converter.py +++ b/pyrit/prompt_converter/diacritic_converter.py @@ -20,7 +20,7 @@ class DiacriticConverter(PromptConverter): def __init__(self, target_chars: str = "aeiou", accent: str = "acute"): """ - Initializes the converter with specified target characters and diacritic accent. + Initialize the converter with specified target characters and diacritic accent. Args: target_chars (str): Characters to apply the diacritic to. Defaults to "aeiou". @@ -45,7 +45,13 @@ def __init__(self, target_chars: str = "aeiou", accent: str = "acute"): def _get_accent_mark(self) -> str: """ - Retrieves the Unicode character for the specified diacritic accent. + Retrieve the Unicode character for the specified diacritic accent. + + Returns: + str: The Unicode diacritic character. + + Raises: + ValueError: If the specified accent is not recognized. """ diacritics = { "acute": "\u0301", # Acute accent @@ -62,7 +68,7 @@ def _get_accent_mark(self) -> str: def _add_diacritic(self, text: str) -> str: """ - Applies the diacritic to each specified target character in the text. + Apply the diacritic to each specified target character in the text. Args: text (str): The input text in which diacritics will be added. @@ -78,7 +84,19 @@ def _add_diacritic(self, text: str) -> str: ) async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: - """Converts the given prompt by applying diacritics to specified characters.""" + """ + Convert the given prompt by applying diacritics to specified characters. + + Args: + prompt (str): The text prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the modified text. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Only 'text' input type is supported.") diff --git a/pyrit/prompt_converter/emoji_converter.py b/pyrit/prompt_converter/emoji_converter.py index c210ab9df3..e2104d6905 100644 --- a/pyrit/prompt_converter/emoji_converter.py +++ b/pyrit/prompt_converter/emoji_converter.py @@ -44,6 +44,15 @@ class EmojiConverter(WordLevelConverter): } async def convert_word_async(self, word: str) -> str: + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ word = word.lower() result = [] for char in word: diff --git a/pyrit/prompt_converter/first_letter_converter.py b/pyrit/prompt_converter/first_letter_converter.py index 57ea7b4b41..826a9c3446 100644 --- a/pyrit/prompt_converter/first_letter_converter.py +++ b/pyrit/prompt_converter/first_letter_converter.py @@ -20,7 +20,7 @@ def __init__( word_selection_strategy: Optional[WordSelectionStrategy] = None, ): """ - Initializes the converter with the specified letter separator and selection strategy. + Initialize the converter with the specified letter separator and selection strategy. Args: letter_separator (str): The string used to join the first letters. @@ -31,9 +31,27 @@ def __init__( self.letter_separator = letter_separator async def convert_word_async(self, word: str) -> str: + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ stripped_word = "".join(filter(str.isalnum, word)) return stripped_word[:1] def join_words(self, words: list[str]) -> str: + """ + Join the converted words using the specified letter separator. + + Args: + words (list[str]): The list of converted words. + + Returns: + str: The joined string of converted words. + """ cleaned_words = list(filter(None, words)) return self.letter_separator.join(cleaned_words) diff --git a/pyrit/prompt_converter/human_in_the_loop_converter.py b/pyrit/prompt_converter/human_in_the_loop_converter.py index 5b5af3f6d4..72398cb6f6 100644 --- a/pyrit/prompt_converter/human_in_the_loop_converter.py +++ b/pyrit/prompt_converter/human_in_the_loop_converter.py @@ -26,7 +26,7 @@ def __init__( converters: Optional[list[PromptConverter]] = None, ): """ - Initializes the converter with a list of possible converters to run input through. + Initialize the converter with a list of possible converters to run input through. Args: converters (List[PromptConverter], Optional): List of possible converters to run input through. @@ -35,7 +35,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by allowing user interaction before sending it to a target. + Convert the given prompt by allowing user interaction before sending it to a target. User is given three options to choose from: (1) Proceed with sending the prompt as is. diff --git a/pyrit/prompt_converter/image_compression_converter.py b/pyrit/prompt_converter/image_compression_converter.py index d2f5820a7b..a14c2f4301 100644 --- a/pyrit/prompt_converter/image_compression_converter.py +++ b/pyrit/prompt_converter/image_compression_converter.py @@ -61,7 +61,7 @@ def __init__( fallback_to_original: bool = True, ): """ - Initializes the converter with specified compression settings. + Initialize the converter with specified compression settings. Args: output_format (str, optional): Output image format. If None, keeps original format (if supported). @@ -124,13 +124,31 @@ def __init__( ) def _should_compress(self, original_size: int) -> bool: - """Determines if image should be compressed.""" + """ + Determine if image should be compressed. + + Args: + original_size (int): The size of the original image in bytes. + + Returns: + bool: True if the image should be compressed, False otherwise. + """ if original_size < self._min_compression_threshold: return False # skip compression for small images return True def _compress_image(self, image: Image.Image, original_format: str, original_size: int) -> tuple[BytesIO, str]: - """Compresses the image with the specified settings. Returns the compressed image bytes and output format.""" + """ + Compress the image with the specified settings. Returns the compressed image bytes and output format. + + Args: + image (PIL.Image.Image): The image to be compressed. + original_format (str): The original format of the image. + original_size (int): The size of the original image in bytes. + + Returns: + tuple[BytesIO, str]: A tuple containing the compressed image bytes and the output format. + """ original_format = original_format.upper() output_format = self._output_format or ( original_format if original_format in ("JPEG", "PNG", "WEBP") else "JPEG" @@ -184,7 +202,19 @@ def _compress_image(self, image: Image.Image, original_format: str, original_siz async def _handle_original_image_fallback( self, prompt: str, input_type: PromptDataType, img_serializer, original_img_bytes: bytes, original_format: str ) -> ConverterResult: - """Handles fallback to original image for both URL and file path inputs.""" + """ + Handle fallback to original image for both URL and file path inputs. + + Args: + prompt (str): The original prompt (image path or URL). + input_type (PromptDataType): The type of input data. + img_serializer: The data serializer for the image. + original_img_bytes (bytes): The original image bytes. + original_format (str): The original image format. + + Returns: + ConverterResult: The result containing path to the original image. + """ if input_type == "url": # We need to save the downloaded content locally and return the local path img_serializer.file_extension = original_format.lower() @@ -193,7 +223,18 @@ async def _handle_original_image_fallback( return ConverterResult(output_text=prompt, output_type="image_path") async def _read_image_from_url(self, url: str) -> bytes: - """Downloads data from URL and returns the content as bytes.""" + """ + Download data from URL and returns the content as bytes. + + Args: + url (str): The URL to download the image from. + + Returns: + bytes: The content of the image as bytes. + + Raises: + RuntimeError: If there is an error during the download process. + """ try: async with aiohttp.ClientSession() as session: async with session.get(url) as response: @@ -204,7 +245,7 @@ async def _read_image_from_url(self, url: str) -> bytes: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "image_path") -> ConverterResult: """ - Converts the given prompt (image) by compressing it. + Convert the given prompt (image) by compressing it. Args: prompt (str): The image file path or URL pointing to the image to be compressed. diff --git a/pyrit/prompt_converter/insert_punctuation_converter.py b/pyrit/prompt_converter/insert_punctuation_converter.py index 929f241af9..885fb64da2 100644 --- a/pyrit/prompt_converter/insert_punctuation_converter.py +++ b/pyrit/prompt_converter/insert_punctuation_converter.py @@ -27,7 +27,7 @@ class InsertPunctuationConverter(PromptConverter): def __init__(self, word_swap_ratio: float = 0.2, between_words: bool = True) -> None: """ - Initializes the converter with a word swap ratio and punctuation insertion mode. + Initialize the converter with a word swap ratio and punctuation insertion mode. Args: word_swap_ratio (float): Percentage of words to perturb. Defaults to 0.2. @@ -61,7 +61,7 @@ async def convert_async( self, *, prompt: str, input_type: PromptDataType = "text", punctuation_list: Optional[List[str]] = None ) -> ConverterResult: """ - Converts the given prompt by inserting punctuation. + Convert the given prompt by inserting punctuation. Args: prompt (str): The text to convert. @@ -151,7 +151,7 @@ def _insert_within_words(self, prompt: str, num_insertions: int, punctuation_lis Insert punctuation at any indices in the prompt, can insert into a word. Args: - promp str: The prompt string + prompt (str): The prompt string num_insertions (int): Number of punctuations to insert. punctuation_list (List[str]): punctuations for insertion. diff --git a/pyrit/prompt_converter/leetspeak_converter.py b/pyrit/prompt_converter/leetspeak_converter.py index fa48cb15db..387e484ade 100644 --- a/pyrit/prompt_converter/leetspeak_converter.py +++ b/pyrit/prompt_converter/leetspeak_converter.py @@ -21,7 +21,7 @@ def __init__( word_selection_strategy: Optional[WordSelectionStrategy] = None, ): """ - Initializes the converter with optional deterministic mode and custom substitutions. + Initialize the converter with optional deterministic mode and custom substitutions. Args: deterministic (bool): If True, use the first substitution for each character. @@ -51,6 +51,15 @@ def __init__( self._deterministic = deterministic async def convert_word_async(self, word: str) -> str: + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ converted_word = [] for char in word: lower_char = char.lower() diff --git a/pyrit/prompt_converter/llm_generic_text_converter.py b/pyrit/prompt_converter/llm_generic_text_converter.py index 199fda4437..c6a74327e5 100644 --- a/pyrit/prompt_converter/llm_generic_text_converter.py +++ b/pyrit/prompt_converter/llm_generic_text_converter.py @@ -36,7 +36,7 @@ def __init__( **kwargs, ): """ - Initializes the converter with a target and optional prompt templates. + Initialize the converter with a target and optional prompt templates. Args: converter_target (PromptChatTarget): The endpoint that converts the prompt. @@ -63,7 +63,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt using an LLM via the specified converter target. + Convert the given prompt using an LLM via the specified converter target. Args: prompt (str): The prompt to be converted. @@ -71,6 +71,9 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text Returns: ConverterResult: The result containing the converted output and its type. + + Raises: + ValueError: If the input type is not supported. """ conversation_id = str(uuid.uuid4()) diff --git a/pyrit/prompt_converter/malicious_question_generator_converter.py b/pyrit/prompt_converter/malicious_question_generator_converter.py index faa8d711df..41a7848458 100644 --- a/pyrit/prompt_converter/malicious_question_generator_converter.py +++ b/pyrit/prompt_converter/malicious_question_generator_converter.py @@ -30,7 +30,7 @@ def __init__( prompt_template: Optional[SeedPrompt] = None, ): """ - Initializes the converter with a specific target and template. + Initialize the converter with a specific target and template. Args: converter_target (PromptChatTarget): The endpoint that converts the prompt. @@ -49,12 +49,40 @@ def __init__( super().__init__(converter_target=converter_target, system_prompt_template=prompt_template) async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: + """ + Convert the input prompt into malicious questions. + + Args: + prompt (str): The input prompt to convert. + input_type (PromptDataType): The type of the input prompt. Must be "text". + + Returns: + ConverterResult: The result of the conversion. + """ # Add the prompt to _prompt_kwargs before calling the base method self._prompt_kwargs["prompt"] = prompt return await super().convert_async(prompt=prompt, input_type=input_type) def input_supported(self, input_type: PromptDataType) -> bool: + """ + Check if the input type is supported. + + Args: + input_type (PromptDataType): The type of the input prompt. + + Returns: + bool: True if the input type is "text", False otherwise. + """ return input_type == "text" def output_supported(self, output_type: PromptDataType) -> bool: + """ + Check if the output type is supported. + + Args: + output_type (PromptDataType): The desired type of the output prompt. + + Returns: + bool: True if the output type is "text", False otherwise. + """ return output_type == "text" diff --git a/pyrit/prompt_converter/math_prompt_converter.py b/pyrit/prompt_converter/math_prompt_converter.py index cdb707575a..fd6491bbc1 100644 --- a/pyrit/prompt_converter/math_prompt_converter.py +++ b/pyrit/prompt_converter/math_prompt_converter.py @@ -30,7 +30,7 @@ def __init__( prompt_template: Optional[SeedPrompt] = None, ): """ - Initializes the converter with a specific target and template. + Initialize the converter with a specific target and template. Args: converter_target (PromptChatTarget): The endpoint that converts the prompt. @@ -48,7 +48,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt into a mathematical problem format. + Convert the given prompt into a mathematical problem format. Args: prompt (str): The prompt to be converted. diff --git a/pyrit/prompt_converter/morse_converter.py b/pyrit/prompt_converter/morse_converter.py index bf9875f15c..ff8548b9ea 100644 --- a/pyrit/prompt_converter/morse_converter.py +++ b/pyrit/prompt_converter/morse_converter.py @@ -21,7 +21,7 @@ class MorseConverter(PromptConverter): def __init__(self, *, append_description: bool = False) -> None: """ - Initializes the converter with an option to append a description to the prompt. + Initialize the converter with an option to append a description to the prompt. Args: append_description (bool): Append plaintext "expert" text to the prompt. Includes instructions to only @@ -36,7 +36,17 @@ def __init__(self, *, append_description: bool = False) -> None: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt to morse code. + Convert the given prompt to morse code. + + Args: + prompt (str): The prompt to be converted. + input_type (PromptDataType, optional): Type of input data. Defaults to "text". + + Returns: + ConverterResult: The result containing the morse code representation of the prompt. + + Raises: + ValueError: If the input type is not supported. """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/nato_converter.py b/pyrit/prompt_converter/nato_converter.py index 5860aa3347..4bd211d889 100644 --- a/pyrit/prompt_converter/nato_converter.py +++ b/pyrit/prompt_converter/nato_converter.py @@ -61,7 +61,7 @@ class NatoConverter(PromptConverter): async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given text into NATO phonetic alphabet representation. + Convert the given text into NATO phonetic alphabet representation. Args: prompt (str): The text to be converted to NATO phonetic alphabet. @@ -82,7 +82,7 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text def _convert_to_nato(self, text: str) -> str: """ - Converts text to NATO phonetic alphabet representation. + Convert text to NATO phonetic alphabet representation. Args: text (str): The text to convert. diff --git a/pyrit/prompt_converter/negation_trap_converter.py b/pyrit/prompt_converter/negation_trap_converter.py index 0a9988b07c..30974ff57f 100644 --- a/pyrit/prompt_converter/negation_trap_converter.py +++ b/pyrit/prompt_converter/negation_trap_converter.py @@ -45,6 +45,9 @@ def __init__( may reveal the correct value when correcting this. trap_template: A custom template string. Must include {prompt} and {wrong_value} placeholders. If None, uses the default denial template. + + Raises: + ValueError: If the trap_template does not contain required placeholders. """ self.wrong_value = wrong_value self.trap_template = trap_template or self.DEFAULT_TEMPLATE @@ -57,7 +60,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the prompt into a negation trap. + Convert the prompt into a negation trap. This technique works by presenting an obviously wrong answer and asking the target to correct it, which may cause it to reveal protected information. @@ -68,6 +71,9 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text Returns: ConverterResult: The prompt converted to a negation trap. + + Raises: + ValueError: If the input type is not supported. """ if not self.input_supported(input_type): raise ValueError("Input type not supported") @@ -82,7 +88,25 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text return ConverterResult(output_text=result, output_type="text") def input_supported(self, input_type: PromptDataType) -> bool: + """ + Check if the input type is supported. + + Args: + input_type: The type of the input prompt. + + Returns: + bool: True if the input type is supported, False otherwise. + """ return input_type == "text" def output_supported(self, output_type: PromptDataType) -> bool: + """ + Check if the output type is supported. + + Args: + output_type: The desired type of the output prompt. + + Returns: + bool: True if the output type is supported, False otherwise. + """ return output_type == "text" diff --git a/pyrit/prompt_converter/noise_converter.py b/pyrit/prompt_converter/noise_converter.py index 6a842b1866..a3c2a3b553 100644 --- a/pyrit/prompt_converter/noise_converter.py +++ b/pyrit/prompt_converter/noise_converter.py @@ -32,7 +32,7 @@ def __init__( prompt_template: Optional[SeedPrompt] = None, ): """ - Initializes the converter with the specified parameters. + Initialize the converter with the specified parameters. Args: converter_target (PromptChatTarget): The endpoint that converts the prompt. diff --git a/pyrit/prompt_converter/pdf_converter.py b/pyrit/prompt_converter/pdf_converter.py index 004d7c3bc6..ccc97b42f7 100644 --- a/pyrit/prompt_converter/pdf_converter.py +++ b/pyrit/prompt_converter/pdf_converter.py @@ -48,7 +48,7 @@ def __init__( injection_items: Optional[List[Dict]] = None, ) -> None: """ - Initializes the converter with the specified parameters. + Initialize the converter with the specified parameters. Args: prompt_template (Optional[SeedPrompt], optional): A ``SeedPrompt`` object representing a template. @@ -104,8 +104,10 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt into a PDF. If a template is provided, it injects the prompt into the template, - otherwise, it generates a simple PDF with the prompt as the content. Further it can modify existing PDFs. + Convert the given prompt into a PDF. + + If a template is provided, it injects the prompt into the template, otherwise, it generates + a simple PDF with the prompt as the content. Further it can modify existing PDFs. Args: prompt (str): The prompt to be embedded in the PDF. @@ -113,6 +115,9 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text Returns: ConverterResult: The result containing the full file path to the generated PDF. + + Raises: + ValueError: If the input type is not supported. """ if not self.input_supported(input_type): raise ValueError("Input type not supported") @@ -134,13 +139,18 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text def _prepare_content(self, prompt: str) -> str: """ - Prepares the content for the PDF, either from a template or directly from the prompt. + Prepare the content for the PDF, either from a template or directly from the prompt. Args: prompt (str): The input prompt. Returns: str: The prepared content. + + Raises: + ValueError: If the parsed dynamic data is not a dictionary. + ValueError: If rendering the prompt fails. + ValueError: If the prompt is not a string when no template is provided. """ if self._prompt_template: logger.debug(f"Preparing content with template: {self._prompt_template.value}") @@ -171,7 +181,7 @@ def _prepare_content(self, prompt: str) -> str: def _generate_pdf(self, content: str) -> bytes: """ - Generates a PDF with the given content using ReportLab. + Generate a PDF with the given content using ReportLab. Args: content (str): The text content to include in the PDF. @@ -242,8 +252,7 @@ def _generate_pdf(self, content: str) -> bytes: def _modify_existing_pdf(self) -> bytes: """ - The method loops over each page, checks for matching injection items, and merges - a small "overlay PDF" for each item. + Loop over each page, check for matching injection items, and merge a small "overlay PDF" for each item. Returns: bytes: The modified PDF content in bytes. @@ -315,7 +324,7 @@ def _inject_text_into_page( self, page: PageObject, x: float, y: float, text: str, font: str, font_size: int, font_color: tuple ) -> tuple[PageObject, BytesIO]: """ - Generates an overlay PDF with the given text using ReportLab. + Generate an overlay PDF with the given text using ReportLab. Args: page (PageObject): The original PDF page to overlay on. @@ -328,6 +337,9 @@ def _inject_text_into_page( Returns: tuple[PageObject, BytesIO]: The overlay page object and its corresponding buffer. + + Raises: + ValueError: If the coordinates are out of bounds. """ from reportlab.pdfgen import canvas @@ -382,7 +394,7 @@ def _inject_text_into_page( async def _serialize_pdf(self, pdf_bytes: bytes, content: str): """ - Serializes the generated PDF using a data serializer. + Serialize the generated PDF using a data serializer. Args: pdf_bytes (bytes): The generated PDF content in bytes. diff --git a/pyrit/prompt_converter/persuasion_converter.py b/pyrit/prompt_converter/persuasion_converter.py index 15d21cae09..5529f72f20 100644 --- a/pyrit/prompt_converter/persuasion_converter.py +++ b/pyrit/prompt_converter/persuasion_converter.py @@ -55,7 +55,7 @@ def __init__( persuasion_technique: str, ): """ - Initializes the converter with the specified target and prompt template. + Initialize the converter with the specified target and prompt template. Args: converter_target (PromptChatTarget): The chat target used to perform rewriting on user prompts. @@ -80,7 +80,17 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt using the persuasion technique specified during initialization. + Convert the given prompt using the persuasion technique specified during initialization. + + Args: + prompt (str): The input prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the converted prompt text. + + Raises: + ValueError: If the input type is not supported. """ if not self.input_supported(input_type): raise ValueError("Input type not supported") @@ -114,8 +124,19 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text return ConverterResult(output_text=response, output_type="text") @pyrit_json_retry - async def send_persuasion_prompt_async(self, request): - """Sends the prompt to the converter target and processes the response.""" + async def send_persuasion_prompt_async(self, request: Message) -> str: + """ + Send the prompt to the converter target and process the response. + + Args: + request (Message): The message containing the prompt to be converted. + + Returns: + str: The converted prompt text extracted from the response. + + Raises: + InvalidJsonException: If the response is not valid JSON or missing expected keys. + """ response = await self.converter_target.send_prompt_async(message=request) response_msg = response[0].get_value() diff --git a/pyrit/prompt_converter/prompt_converter.py b/pyrit/prompt_converter/prompt_converter.py index 4be2729f14..9dd8085b87 100644 --- a/pyrit/prompt_converter/prompt_converter.py +++ b/pyrit/prompt_converter/prompt_converter.py @@ -22,6 +22,12 @@ class ConverterResult: output_type: PromptDataType def __str__(self): + """ + Representation of the ConverterResult. + + Returns: + str: A string representation showing the output type and text. + """ return f"{self.output_type}: {self.output_text}" @@ -43,7 +49,7 @@ class PromptConverter(abc.ABC, Identifier): def __init_subclass__(cls, **kwargs) -> None: """ - Validates that concrete subclasses define required class attributes. + Validate that concrete subclasses define required class attributes. Args: **kwargs: Additional keyword arguments passed to the superclass. @@ -68,14 +74,14 @@ def __init_subclass__(cls, **kwargs) -> None: def __init__(self): """ - Initializes the prompt converter. + Initialize the prompt converter. """ super().__init__() @abc.abstractmethod async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt into the target format supported by the converter. + Convert the given prompt into the target format supported by the converter. Args: prompt (str): The prompt to be converted. @@ -87,7 +93,7 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text def input_supported(self, input_type: PromptDataType) -> bool: """ - Checks if the input type is supported by the converter. + Check if the input type is supported by the converter. Args: input_type (PromptDataType): The input type to check. @@ -99,7 +105,7 @@ def input_supported(self, input_type: PromptDataType) -> bool: def output_supported(self, output_type: PromptDataType) -> bool: """ - Checks if the output type is supported by the converter. + Check if the output type is supported by the converter. Args: output_type (PromptDataType): The output type to check. @@ -113,7 +119,7 @@ async def convert_tokens_async( self, *, prompt: str, input_type: PromptDataType = "text", start_token: str = "⟪", end_token: str = "⟫" ) -> ConverterResult: """ - Converts substrings within a prompt that are enclosed by specified start and end tokens. If there are no tokens + Convert substrings within a prompt that are enclosed by specified start and end tokens. If there are no tokens present, the entire prompt is converted. Args: @@ -158,7 +164,7 @@ async def _replace_text_match(self, match): def get_identifier(self) -> dict[str, str]: """ - Returns an identifier dictionary for the converter. + Return an identifier dictionary for the converter. Returns: dict: The identifier dictionary. @@ -191,7 +197,7 @@ def supported_output_types(self) -> list[PromptDataType]: def get_converter_modalities() -> list[tuple[str, list[PromptDataType], list[PromptDataType]]]: """ - Retrieves a list of all converter classes and their supported input/output modalities + Retrieve a list of all converter classes and their supported input/output modalities by reading the SUPPORTED_INPUT_TYPES and SUPPORTED_OUTPUT_TYPES class attributes. Returns: diff --git a/pyrit/prompt_converter/qr_code_converter.py b/pyrit/prompt_converter/qr_code_converter.py index 829b343f7f..865c4f4186 100644 --- a/pyrit/prompt_converter/qr_code_converter.py +++ b/pyrit/prompt_converter/qr_code_converter.py @@ -28,7 +28,7 @@ def __init__( border_color: Optional[tuple] = None, ): """ - Initializes the converter with specified parameters for QR code generation. + Initialize the converter with specified parameters for QR code generation. Args: scale (int, Optional): Scaling factor that determines the width/height in pixels of each @@ -62,7 +62,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt to a QR code image. + Convert the given prompt to a QR code image. Args: prompt (str): The prompt to be converted. diff --git a/pyrit/prompt_converter/random_capital_letters_converter.py b/pyrit/prompt_converter/random_capital_letters_converter.py index 9b7a1862c7..df71391745 100644 --- a/pyrit/prompt_converter/random_capital_letters_converter.py +++ b/pyrit/prompt_converter/random_capital_letters_converter.py @@ -18,7 +18,7 @@ class RandomCapitalLettersConverter(PromptConverter): def __init__(self, percentage: float = 100.0) -> None: """ - Initializes the converter with the specified percentage of randomization. + Initialize the converter with the specified percentage of randomization. Args: percentage (float): The percentage of characters to capitalize in the prompt. Must be between 1 and 100. @@ -26,12 +26,16 @@ def __init__(self, percentage: float = 100.0) -> None: """ self.percentage = percentage - def is_lowercase_letter(self, char): - """Checks if the given character is a lowercase letter.""" - return char.islower() - def is_percentage(self, input_string): - """Checks if the input string is a valid percentage between 1 and 100.""" + """ + Check if the input string is a valid percentage between 1 and 100. + + Args: + input_string (str): The input string to check. + + Returns: + bool: True if the input string is a valid percentage, False otherwise. + """ try: number = float(input_string) return 1 <= number <= 100 @@ -39,7 +43,19 @@ def is_percentage(self, input_string): return False def generate_random_positions(self, total_length, set_number): - """Generates a list of unique random positions within the range of `total_length`.""" + """ + Generate a list of unique random positions within the range of `total_length`. + + Args: + total_length (int): The total length of the string. + set_number (int): The number of unique random positions to generate. + + Returns: + list: A list of unique random positions. + + Raises: + ValueError: If `set_number` is greater than `total_length`. + """ # Ensure the set number is not greater than the total length if set_number > total_length: logger.error(f"Set number {set_number} cannot be greater than the total length which is {total_length}.") @@ -53,7 +69,19 @@ def generate_random_positions(self, total_length, set_number): return random_positions def string_to_upper_case_by_percentage(self, percentage, prompt): - """Converts a string by randomly capitalizing a percentage of its characters.""" + """ + Convert a string by randomly capitalizing a percentage of its characters. + + Args: + percentage (float): The percentage of characters to capitalize. + prompt (str): The input string to be converted. + + Returns: + str: The converted string with randomly capitalized characters. + + Raises: + ValueError: If the percentage is not between 1 and 100. + """ if not self.is_percentage(percentage): logger.error(f"Percentage number {percentage} cannot be higher than 100 and lower than 1.") raise ValueError(f"Percentage number {percentage} cannot be higher than 100 and lower than 1.") @@ -61,13 +89,23 @@ def string_to_upper_case_by_percentage(self, percentage, prompt): random_positions = self.generate_random_positions(len(prompt), target_count) output = list(prompt) for pos in random_positions: - if self.is_lowercase_letter(prompt[pos]): + if prompt[pos].islower(): output[pos] = prompt[pos].upper() return "".join(output) async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by randomly capitalizing a percentage of its characters. + Convert the given prompt by randomly capitalizing a percentage of its characters. + + Args: + prompt (str): The input text prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the converted text. + + Raises: + ValueError: If the input type is not supported. """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/random_translation_converter.py b/pyrit/prompt_converter/random_translation_converter.py index e1af936b98..185f9d4ef5 100644 --- a/pyrit/prompt_converter/random_translation_converter.py +++ b/pyrit/prompt_converter/random_translation_converter.py @@ -41,7 +41,7 @@ def __init__( word_selection_strategy: Optional[WordSelectionStrategy] = None, ): """ - Initializes the converter with a target, an optional system prompt template, and language options. + Initialize the converter with a target, an optional system prompt template, and language options. Args: converter_target (PromptChatTarget): The target for the prompt conversion. @@ -86,7 +86,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt into the target format supported by the converter. + Convert the given prompt into the target format supported by the converter. Args: prompt (str): The prompt to be converted. @@ -112,7 +112,7 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text async def convert_word_async(self, word: str) -> str: """ - Converts a single word into the target format supported by the converter. + Convert a single word into the target format supported by the converter. Args: word (str): The word to be converted. diff --git a/pyrit/prompt_converter/repeat_token_converter.py b/pyrit/prompt_converter/repeat_token_converter.py index 97615b5f74..90cef5d702 100644 --- a/pyrit/prompt_converter/repeat_token_converter.py +++ b/pyrit/prompt_converter/repeat_token_converter.py @@ -38,7 +38,7 @@ def __init__( token_insert_mode: Optional[Literal["split", "prepend", "append", "repeat"]] = None, ) -> None: """ - Initializes the converter with the specified token, number of repetitions, and insertion mode. + Initialize the converter with the specified token, number of repetitions, and insertion mode. Args: token_to_repeat (str): The string to be repeated. @@ -82,7 +82,17 @@ def insert(text: str) -> list: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by repeating the specified token a specified number of times. + Convert the given prompt by repeating the specified token a specified number of times. + + Args: + prompt (str): The prompt to be converted. + input_type (PromptDataType): The type of the input prompt. + + Returns: + ConverterResult: The result containing the modified prompt with repeated tokens. + + Raises: + ValueError: If the input type is not supported. """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/rot13_converter.py b/pyrit/prompt_converter/rot13_converter.py index a4686fdd30..11b6ae2d82 100644 --- a/pyrit/prompt_converter/rot13_converter.py +++ b/pyrit/prompt_converter/rot13_converter.py @@ -12,4 +12,13 @@ class ROT13Converter(WordLevelConverter): """ async def convert_word_async(self, word: str) -> str: + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ return codecs.encode(word, "rot13") diff --git a/pyrit/prompt_converter/search_replace_converter.py b/pyrit/prompt_converter/search_replace_converter.py index 35d40599a7..b944fbc2cf 100644 --- a/pyrit/prompt_converter/search_replace_converter.py +++ b/pyrit/prompt_converter/search_replace_converter.py @@ -18,7 +18,7 @@ class SearchReplaceConverter(PromptConverter): def __init__(self, pattern: str, replace: str | list[str], regex_flags=0) -> None: """ - Initializes the converter with the specified regex pattern and replacement phrase(s). + Initialize the converter with the specified regex pattern and replacement phrase(s). Args: pattern (str): The regex pattern to replace. @@ -33,7 +33,7 @@ def __init__(self, pattern: str, replace: str | list[str], regex_flags=0) -> Non async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by replacing the specified pattern with a random choice from the replacement list. + Convert the given prompt by replacing the specified pattern with a random choice from the replacement list. Args: prompt (str): The prompt to be converted. diff --git a/pyrit/prompt_converter/selective_text_converter.py b/pyrit/prompt_converter/selective_text_converter.py index e9c21cc9ca..093348ddd8 100644 --- a/pyrit/prompt_converter/selective_text_converter.py +++ b/pyrit/prompt_converter/selective_text_converter.py @@ -55,7 +55,7 @@ def __init__( word_separator: str = " ", ) -> None: """ - Initializes the selective text converter. + Initialize the selective text converter. Args: converter (PromptConverter): The converter to apply to the selected text. @@ -96,7 +96,7 @@ def _validate_converter( selection_strategy: TextSelectionStrategy, ) -> None: """ - Validates the converter and selection strategy combination. + Validate the converter and selection strategy combination. Args: converter (PromptConverter): The converter to validate. @@ -127,7 +127,7 @@ def _validate_converter( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts selected portions of the prompt using the wrapped converter. + Convert selected portions of the prompt using the wrapped converter. Args: prompt (str): The prompt to be converted. @@ -165,7 +165,15 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text return await self._convert_char_level_async(prompt=prompt) async def _convert_word_level_async(self, *, prompt: str) -> ConverterResult: - """Converts selected words using word-level selection strategy.""" + """ + Convert selected words using word-level selection strategy. + + Args: + prompt (str): The prompt to be converted. + + Returns: + ConverterResult: The result containing the converted output and its type. + """ words = prompt.split(self._word_separator) # Get selected word indices @@ -189,7 +197,15 @@ async def _convert_word_level_async(self, *, prompt: str) -> ConverterResult: return ConverterResult(output_text=final_text, output_type="text") async def _convert_char_level_async(self, *, prompt: str) -> ConverterResult: - """Converts a character range using character-level selection strategy.""" + """ + Convert a character range using character-level selection strategy. + + Args: + prompt (str): The prompt to be converted. + + Returns: + ConverterResult: The result containing the converted output and its type. + """ start_idx, end_idx = self._selection_strategy.select_range(text=prompt) # If no region selected, return original prompt diff --git a/pyrit/prompt_converter/string_join_converter.py b/pyrit/prompt_converter/string_join_converter.py index c4b6626c91..15e84d5ea4 100644 --- a/pyrit/prompt_converter/string_join_converter.py +++ b/pyrit/prompt_converter/string_join_converter.py @@ -19,7 +19,7 @@ def __init__( word_selection_strategy: Optional[WordSelectionStrategy] = None, ): """ - Initializes the converter with the specified join value and selection strategy. + Initialize the converter with the specified join value and selection strategy. Args: join_value (str): The string used to join characters of each word. @@ -30,4 +30,13 @@ def __init__( self.join_value = join_value async def convert_word_async(self, word: str) -> str: + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ return self.join_value.join(word) diff --git a/pyrit/prompt_converter/suffix_append_converter.py b/pyrit/prompt_converter/suffix_append_converter.py index e2a09449da..86a8c4323e 100644 --- a/pyrit/prompt_converter/suffix_append_converter.py +++ b/pyrit/prompt_converter/suffix_append_converter.py @@ -17,12 +17,34 @@ class SuffixAppendConverter(PromptConverter): SUPPORTED_OUTPUT_TYPES = ("text",) def __init__(self, *, suffix: str): + """ + Initialize the converter with the specified suffix. + + Args: + suffix (str): The suffix to append to the prompt. + + Raises: + ValueError: If ``suffix`` is not provided. + """ if not suffix: raise ValueError("Please specify a suffix (str) to be appended to the prompt.") self.suffix = suffix async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: + """ + Convert the given prompt by appending the specified suffix. + + Args: + prompt (str): The prompt to be converted. + input_type (PromptDataType, optional): Type of input data. Defaults to "text". + + Returns: + ConverterResult: The prompt with the suffix appended. + + Raises: + ValueError: If the input type is not supported. + """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/superscript_converter.py b/pyrit/prompt_converter/superscript_converter.py index 5ee8bea15b..3751025f5f 100644 --- a/pyrit/prompt_converter/superscript_converter.py +++ b/pyrit/prompt_converter/superscript_converter.py @@ -74,6 +74,15 @@ class SuperscriptConverter(WordLevelConverter): } async def convert_word_async(self, word: str) -> str: + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ result = [] for char in word: if char in self._superscript_map: diff --git a/pyrit/prompt_converter/template_segment_converter.py b/pyrit/prompt_converter/template_segment_converter.py index d10b3f4c93..7372751080 100644 --- a/pyrit/prompt_converter/template_segment_converter.py +++ b/pyrit/prompt_converter/template_segment_converter.py @@ -30,7 +30,7 @@ def __init__( prompt_template: Optional[SeedPrompt] = None, ): """ - Initializes the converter with the specified target and prompt template. + Initialize the converter with the specified target and prompt template. Args: prompt_template (SeedPrompt, Optional): The prompt template for the conversion. Must have two or more @@ -71,7 +71,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by splitting it into random segments and using them to fill the template parameters. + Convert the given prompt by splitting it into random segments and using them to fill the template parameters. The prompt is split into N segments (where N is the number of template parameters) at random word boundaries. Each segment is then used to fill the corresponding template parameter. @@ -96,7 +96,7 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text def _split_prompt_into_segments(self, prompt: str) -> list[str]: """ - Splits a prompt into random segments based on word boundaries. + Split a prompt into random segments based on word boundaries. If there aren't enough words for all parameters, remaining segments will be empty strings. Args: diff --git a/pyrit/prompt_converter/tense_converter.py b/pyrit/prompt_converter/tense_converter.py index a1af84661c..32956a8653 100644 --- a/pyrit/prompt_converter/tense_converter.py +++ b/pyrit/prompt_converter/tense_converter.py @@ -30,12 +30,12 @@ def __init__( prompt_template: Optional[SeedPrompt] = None, ): """ - Initializes the converter with the target chat support, tense, and optional prompt template. + Initialize the converter with the target chat support, tense, and optional prompt template. Args: converter_target (PromptChatTarget): The target chat support for the conversion which will translate. Can be omitted if a default has been configured via PyRIT initialization. - tone (str): The tense the converter should convert the prompt to. E.g. past, present, future. + tense (str): The tense the converter should convert the prompt to. E.g. past, present, future. prompt_template (SeedPrompt, Optional): The prompt template for the conversion. """ # set to default strategy if not provided diff --git a/pyrit/prompt_converter/text_jailbreak_converter.py b/pyrit/prompt_converter/text_jailbreak_converter.py index 5483e0b943..3e8f5c9695 100644 --- a/pyrit/prompt_converter/text_jailbreak_converter.py +++ b/pyrit/prompt_converter/text_jailbreak_converter.py @@ -16,7 +16,7 @@ class TextJailbreakConverter(PromptConverter): def __init__(self, *, jailbreak_template: TextJailBreak): """ - Initializes the converter with the specified jailbreak template. + Initialize the converter with the specified jailbreak template. Args: jailbreak_template (TextJailBreak): The jailbreak template to use for conversion. @@ -25,7 +25,17 @@ def __init__(self, *, jailbreak_template: TextJailBreak): async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt using the jailbreak template. + Convert the given prompt using the jailbreak template. + + Args: + prompt (str): The prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the converted output and its type. + + Raises: + ValueError: If the input type is not supported. """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/text_selection_strategy.py b/pyrit/prompt_converter/text_selection_strategy.py index 04b9724333..421ac09b6f 100644 --- a/pyrit/prompt_converter/text_selection_strategy.py +++ b/pyrit/prompt_converter/text_selection_strategy.py @@ -16,7 +16,7 @@ class TextSelectionStrategy(abc.ABC): @abc.abstractmethod def select_range(self, *, text: str) -> tuple[int, int]: """ - Selects a range of characters in the text to be converted. + Select a range of characters in the text to be converted. Args: text (str): The input text to select from. @@ -54,7 +54,7 @@ class TokenSelectionStrategy(TextSelectionStrategy): def select_range(self, *, text: str) -> tuple[int, int]: """ - This method is not used for TokenSelectionStrategy. + Do not use this method for TokenSelectionStrategy. SelectiveTextConverter handles token detection separately. Args: @@ -78,7 +78,7 @@ class WordSelectionStrategy(TextSelectionStrategy): @abc.abstractmethod def select_words(self, *, words: List[str]) -> List[int]: """ - Selects word indices to be converted. + Select word indices to be converted. Args: words (List[str]): The list of words to select from. @@ -90,7 +90,7 @@ def select_words(self, *, words: List[str]) -> List[int]: def select_range(self, *, text: str, word_separator: str = " ") -> tuple[int, int]: """ - Selects a character range by first selecting words, then converting to character positions. + Select a character range by first selecting words, then converting to character positions. This implementation splits the text by word_separator, gets selected word indices, then calculates the character range that spans those words. @@ -136,7 +136,7 @@ class IndexSelectionStrategy(TextSelectionStrategy): def __init__(self, *, start: int = 0, end: Optional[int] = None) -> None: """ - Initializes the index selection strategy. + Initialize the index selection strategy. Args: start (int): The starting character index (inclusive). Defaults to 0. @@ -147,7 +147,7 @@ def __init__(self, *, start: int = 0, end: Optional[int] = None) -> None: def select_range(self, *, text: str) -> tuple[int, int]: """ - Selects a range based on absolute character indices. + Select a range based on absolute character indices. Args: text (str): The input text to select from. @@ -168,7 +168,7 @@ class RegexSelectionStrategy(TextSelectionStrategy): def __init__(self, *, pattern: Union[str, Pattern]) -> None: """ - Initializes the regex selection strategy. + Initialize the regex selection strategy. Args: pattern (Union[str, Pattern]): The regex pattern to match. @@ -177,7 +177,7 @@ def __init__(self, *, pattern: Union[str, Pattern]) -> None: def select_range(self, *, text: str) -> tuple[int, int]: """ - Selects the range of the first regex match. + Select the range of the first regex match. Args: text (str): The input text to select from. @@ -206,7 +206,7 @@ def __init__( case_sensitive: bool = True, ) -> None: """ - Initializes the keyword selection strategy. + Initialize the keyword selection strategy. Args: keyword (str): The keyword to search for. @@ -221,7 +221,7 @@ def __init__( def select_range(self, *, text: str) -> tuple[int, int]: """ - Selects the range around the first occurrence of the keyword. + Select the range around the first occurrence of the keyword. Args: text (str): The input text to select from. @@ -249,7 +249,7 @@ class PositionSelectionStrategy(TextSelectionStrategy): def __init__(self, *, start_proportion: float, end_proportion: float) -> None: """ - Initializes the position selection strategy. + Initialize the position selection strategy. Args: start_proportion (float): The starting position as a proportion (0.0 to 1.0). @@ -272,7 +272,7 @@ def __init__(self, *, start_proportion: float, end_proportion: float) -> None: def select_range(self, *, text: str) -> tuple[int, int]: """ - Selects a range based on the relative position in the text. + Select a range based on the relative position in the text. Args: text (str): The input text to select from. @@ -293,7 +293,7 @@ class ProportionSelectionStrategy(TextSelectionStrategy): def __init__(self, *, proportion: float, anchor: str = "start", seed: Optional[int] = None) -> None: """ - Initializes the proportion selection strategy. + Initialize the proportion selection strategy. Args: proportion (float): The proportion of text to select (0.0 to 1.0). @@ -320,7 +320,7 @@ def __init__(self, *, proportion: float, anchor: str = "start", seed: Optional[i def select_range(self, *, text: str) -> tuple[int, int]: """ - Selects a proportion of text based on the anchor position. + Select a proportion of text based on the anchor position. Args: text (str): The input text to select from. @@ -353,7 +353,7 @@ class RangeSelectionStrategy(TextSelectionStrategy): def __init__(self, *, start_proportion: float = 0.0, end_proportion: float = 1.0) -> None: """ - Initializes the range selection strategy. + Initialize the range selection strategy. Args: start_proportion (float): The starting position as a proportion (0.0 to 1.0). Defaults to 0.0. @@ -376,7 +376,7 @@ def __init__(self, *, start_proportion: float = 0.0, end_proportion: float = 1.0 def select_range(self, *, text: str) -> tuple[int, int]: """ - Selects a range based on proportional positions. + Select a range based on proportional positions. Args: text (str): The input text to select from. @@ -402,7 +402,7 @@ class WordIndexSelectionStrategy(WordSelectionStrategy): def __init__(self, *, indices: List[int]) -> None: """ - Initializes the word index selection strategy. + Initialize the word index selection strategy. Args: indices (List[int]): The list of word indices to select. @@ -411,7 +411,7 @@ def __init__(self, *, indices: List[int]) -> None: def select_words(self, *, words: List[str]) -> List[int]: """ - Selects words at the specified indices. + Select words at the specified indices. Args: words (List[str]): The list of words to select from. @@ -441,7 +441,7 @@ class WordKeywordSelectionStrategy(WordSelectionStrategy): def __init__(self, *, keywords: List[str], case_sensitive: bool = True) -> None: """ - Initializes the word keyword selection strategy. + Initialize the word keyword selection strategy. Args: keywords (List[str]): The list of keywords to match. @@ -452,7 +452,7 @@ def __init__(self, *, keywords: List[str], case_sensitive: bool = True) -> None: def select_words(self, *, words: List[str]) -> List[int]: """ - Selects words that match the keywords. + Select words that match the keywords. Args: words (List[str]): The list of words to select from. @@ -477,7 +477,7 @@ class WordProportionSelectionStrategy(WordSelectionStrategy): def __init__(self, *, proportion: float, seed: Optional[int] = None) -> None: """ - Initializes the word proportion selection strategy. + Initialize the word proportion selection strategy. Args: proportion (float): The proportion of words to select (0.0 to 1.0). @@ -494,7 +494,7 @@ def __init__(self, *, proportion: float, seed: Optional[int] = None) -> None: def select_words(self, *, words: List[str]) -> List[int]: """ - Selects a random proportion of words. + Select a random proportion of words. Args: words (List[str]): The list of words to select from. @@ -519,7 +519,7 @@ class WordRegexSelectionStrategy(WordSelectionStrategy): def __init__(self, *, pattern: Union[str, Pattern]) -> None: """ - Initializes the word regex selection strategy. + Initialize the word regex selection strategy. Args: pattern (Union[str, Pattern]): The regex pattern to match against words. @@ -528,7 +528,7 @@ def __init__(self, *, pattern: Union[str, Pattern]) -> None: def select_words(self, *, words: List[str]) -> List[int]: """ - Selects words that match the regex pattern. + Select words that match the regex pattern. Args: words (List[str]): The list of words to select from. @@ -549,7 +549,7 @@ class WordPositionSelectionStrategy(WordSelectionStrategy): def __init__(self, *, start_proportion: float, end_proportion: float) -> None: """ - Initializes the word position selection strategy. + Initialize the word position selection strategy. Args: start_proportion (float): The starting position as a proportion (0.0 to 1.0). @@ -572,7 +572,7 @@ def __init__(self, *, start_proportion: float, end_proportion: float) -> None: def select_words(self, *, words: List[str]) -> List[int]: """ - Selects words based on the relative position. + Select words based on the relative position. Args: words (List[str]): The list of words to select from. @@ -597,7 +597,7 @@ class AllWordsSelectionStrategy(WordSelectionStrategy): def select_words(self, *, words: List[str]) -> List[int]: """ - Selects all words. + Select all words. Args: words (List[str]): The list of words to select from. diff --git a/pyrit/prompt_converter/token_smuggling/__init__.py b/pyrit/prompt_converter/token_smuggling/__init__.py index cff7563acb..30fb80bd30 100644 --- a/pyrit/prompt_converter/token_smuggling/__init__.py +++ b/pyrit/prompt_converter/token_smuggling/__init__.py @@ -1,6 +1,11 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +""" +Token smuggling converters that use Unicode-based techniques to hide, encode, +or obfuscate text content within prompts for security testing purposes. +""" + from pyrit.prompt_converter.token_smuggling.ascii_smuggler_converter import AsciiSmugglerConverter from pyrit.prompt_converter.token_smuggling.sneaky_bits_smuggler_converter import SneakyBitsSmugglerConverter from pyrit.prompt_converter.token_smuggling.variation_selector_smuggler_converter import ( diff --git a/pyrit/prompt_converter/token_smuggling/ascii_smuggler_converter.py b/pyrit/prompt_converter/token_smuggling/ascii_smuggler_converter.py index f64aea3634..1bbec522c3 100644 --- a/pyrit/prompt_converter/token_smuggling/ascii_smuggler_converter.py +++ b/pyrit/prompt_converter/token_smuggling/ascii_smuggler_converter.py @@ -23,7 +23,7 @@ class AsciiSmugglerConverter(SmugglerConverter): def __init__(self, action: Literal["encode", "decode"] = "encode", unicode_tags: bool = False): """ - Initializes the converter with options for encoding/decoding. + Initialize the converter with options for encoding/decoding. Args: action (Literal["encode", "decode"]): The action to perform. @@ -34,7 +34,7 @@ def __init__(self, action: Literal["encode", "decode"] = "encode", unicode_tags: def encode_message(self, *, message: str): """ - Encodes the message using Unicode Tags. + Encode the message using Unicode Tags. Each ASCII printable character (0x20-0x7E) is mapped to a corresponding Unicode Tag (by adding 0xE0000). If control mode is enabled, wraps the output. @@ -68,7 +68,7 @@ def encode_message(self, *, message: str): def decode_message(self, *, message: str): """ - Decodes a message encoded with Unicode Tags. + Decode a message encoded with Unicode Tags. For each character in the Unicode Tags range, subtracts 0xE0000. Skips control tags if present. diff --git a/pyrit/prompt_converter/token_smuggling/base.py b/pyrit/prompt_converter/token_smuggling/base.py index cb945c2591..1c4d00798b 100644 --- a/pyrit/prompt_converter/token_smuggling/base.py +++ b/pyrit/prompt_converter/token_smuggling/base.py @@ -24,10 +24,13 @@ class SmugglerConverter(PromptConverter, abc.ABC): def __init__(self, action: Literal["encode", "decode"] = "encode") -> None: """ - Initializes the converter with options for encoding/decoding. + Initialize the converter with options for encoding/decoding. Args: action (Literal["encode", "decode"]): The action to perform. + + Raises: + ValueError: If the action is not 'encode' or 'decode'. """ if action not in ["encode", "decode"]: raise ValueError("Action must be either 'encode' or 'decode'") @@ -35,7 +38,7 @@ def __init__(self, action: Literal["encode", "decode"] = "encode") -> None: async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by either encoding or decoding it based on the specified action. + Convert the given prompt by either encoding or decoding it based on the specified action. Args: prompt (str): The prompt to be converted. @@ -58,15 +61,33 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text return ConverterResult(output_text=decoded, output_type="text") def input_supported(self, input_type: PromptDataType) -> bool: + """ + Check if the input type is supported by the converter. + + Args: + input_type (PromptDataType): The input type to check. + + Returns: + bool: True if the input type is supported, False otherwise. + """ return input_type == "text" def output_supported(self, output_type: PromptDataType) -> bool: + """ + Check if the output type is supported. + + Args: + output_type (PromptDataType): The type of output data. + + Returns: + bool: True if the output type is supported, False otherwise. + """ return output_type == "text" @abc.abstractmethod def encode_message(self, *, message: str) -> Tuple[str, str]: """ - Encodes the given message. + Encode the given message. Must be implemented by subclasses. @@ -81,7 +102,7 @@ def encode_message(self, *, message: str) -> Tuple[str, str]: @abc.abstractmethod def decode_message(self, *, message: str) -> str: """ - Decodes the given message. + Decode the given message. Must be implemented by subclasses. diff --git a/pyrit/prompt_converter/token_smuggling/sneaky_bits_smuggler_converter.py b/pyrit/prompt_converter/token_smuggling/sneaky_bits_smuggler_converter.py index 9801be31cf..4e9ea19477 100644 --- a/pyrit/prompt_converter/token_smuggling/sneaky_bits_smuggler_converter.py +++ b/pyrit/prompt_converter/token_smuggling/sneaky_bits_smuggler_converter.py @@ -28,7 +28,7 @@ def __init__( one_char: Optional[str] = None, ): """ - Initializes the converter with options for encoding/decoding in Sneaky Bits mode. + Initialize the converter with options for encoding/decoding in Sneaky Bits mode. Args: action (Literal["encode", "decode"]): The action to perform. @@ -44,7 +44,7 @@ def __init__( def encode_message(self, message: str) -> Tuple[str, str]: """ - Encodes the message using Sneaky Bits mode. + Encode the message using Sneaky Bits mode. The message is first converted to its UTF-8 byte sequence. Then each byte is represented as 8 bits, with each bit replaced by an invisible character (``self.zero_char`` for 0 and ``self.one_char`` for 1). @@ -72,7 +72,7 @@ def encode_message(self, message: str) -> Tuple[str, str]: def decode_message(self, message: str) -> str: """ - Decodes the message encoded using Sneaky Bits mode. + Decode the message encoded using Sneaky Bits mode. The method filters out only the valid invisible characters (``self.zero_char`` and ``self.one_char``), groups them into 8-bit chunks, reconstructs each byte, and finally decodes the byte sequence using UTF-8. diff --git a/pyrit/prompt_converter/token_smuggling/variation_selector_smuggler_converter.py b/pyrit/prompt_converter/token_smuggling/variation_selector_smuggler_converter.py index a947ab2e25..ed6ad68ac1 100644 --- a/pyrit/prompt_converter/token_smuggling/variation_selector_smuggler_converter.py +++ b/pyrit/prompt_converter/token_smuggling/variation_selector_smuggler_converter.py @@ -35,7 +35,7 @@ def __init__( embed_in_base: bool = True, ): """ - Initializes the converter with options for encoding/decoding. + Initialize the converter with options for encoding/decoding. Args: action (Literal["encode", "decode"]): The action to perform. @@ -53,7 +53,7 @@ def __init__( def encode_message(self, message: str) -> Tuple[str, str]: """ - Encodes the message using Unicode variation selectors. + Encode the message using Unicode variation selectors. The message is converted to UTF-8 bytes, and each byte is mapped to a variation selector: - 0x00-0x0F => U+FE00 to U+FE0F. @@ -61,6 +61,12 @@ def encode_message(self, message: str) -> Tuple[str, str]: If ``embed_in_base`` is True, the payload is embedded directly into the base character; otherwise, a visible separator (a space) is inserted between the base and payload. + + Args: + message (str): The message to encode. + + Returns: + Tuple[str, str]: A tuple containing a summary of the code points and the encoded string. """ payload = "" data = message.encode("utf-8") @@ -86,8 +92,14 @@ def encode_message(self, message: str) -> Tuple[str, str]: def decode_message(self, message: str) -> str: """ - Decodes a message encoded using Unicode variation selectors. + Decode a message encoded using Unicode variation selectors. The decoder scans the string for variation selectors, ignoring any visible separator. + + Args: + message (str): The encoded message. + + Returns: + str: The decoded message. """ bytes_out = bytearray() started = False @@ -118,7 +130,7 @@ def decode_message(self, message: str) -> str: # Extension of Paul Butler's method def encode_visible_hidden(self, visible: str, hidden: str) -> Tuple[str, str]: """ - Combines visible text with hidden text by encoding the hidden text using ``variation_selector_smuggler`` mode. + Combine visible text with hidden text by encoding the hidden text using ``variation_selector_smuggler`` mode. The hidden payload is generated as a composite using the current embedding setting and then appended to the visible text. @@ -137,7 +149,7 @@ def encode_visible_hidden(self, visible: str, hidden: str) -> Tuple[str, str]: # Extension of Paul Butler's method def decode_visible_hidden(self, combined: str) -> Tuple[str, str]: """ - Extracts the visible text and decodes the hidden text from a combined string. + Extract the visible text and decodes the hidden text from a combined string. It searches for the first occurrence of the base character (``self.utf8_base_char``) and treats everything from that point on as the hidden payload. diff --git a/pyrit/prompt_converter/tone_converter.py b/pyrit/prompt_converter/tone_converter.py index eaa45b115d..9b20398464 100644 --- a/pyrit/prompt_converter/tone_converter.py +++ b/pyrit/prompt_converter/tone_converter.py @@ -30,7 +30,7 @@ def __init__( prompt_template: Optional[SeedPrompt] = None, ): """ - Initializes the converter with the target chat support, tone, and optional prompt template. + Initialize the converter with the target chat support, tone, and optional prompt template. Args: converter_target (PromptChatTarget): The target chat support for the conversion which will translate. diff --git a/pyrit/prompt_converter/toxic_sentence_generator_converter.py b/pyrit/prompt_converter/toxic_sentence_generator_converter.py index 303f515980..d3390c6af7 100644 --- a/pyrit/prompt_converter/toxic_sentence_generator_converter.py +++ b/pyrit/prompt_converter/toxic_sentence_generator_converter.py @@ -38,7 +38,7 @@ def __init__( prompt_template: Optional[SeedPrompt] = None, ): """ - Initializes the converter with a specific target and template. + Initialize the converter with a specific target and template. Args: converter_target (PromptChatTarget): The endpoint that converts the prompt. @@ -57,7 +57,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt into a toxic sentence starter. + Convert the given prompt into a toxic sentence starter. Args: prompt (str): The prompt to be converted. @@ -71,7 +71,25 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text return await super().convert_async(prompt=prompt, input_type=input_type) def input_supported(self, input_type: PromptDataType) -> bool: + """ + Check if the input type is supported. + + Args: + input_type (PromptDataType): The type of input data. + + Returns: + bool: True if the input type is supported, False otherwise. + """ return input_type == "text" def output_supported(self, output_type: PromptDataType) -> bool: + """ + Check if the output type is supported. + + Args: + output_type (PromptDataType): The type of output data. + + Returns: + bool: True if the output type is supported, False otherwise. + """ return output_type == "text" diff --git a/pyrit/prompt_converter/translation_converter.py b/pyrit/prompt_converter/translation_converter.py index 5776940f7a..f9c829f76c 100644 --- a/pyrit/prompt_converter/translation_converter.py +++ b/pyrit/prompt_converter/translation_converter.py @@ -47,7 +47,7 @@ def __init__( max_wait_time_in_seconds: int = 60, ): """ - Initializes the converter with the target chat support, language, and optional prompt template. + Initialize the converter with the target chat support, language, and optional prompt template. Args: converter_target (PromptChatTarget): The target chat support for the conversion which will translate. @@ -83,10 +83,11 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by translating it using the converter target. + Convert the given prompt by translating it using the converter target. Args: prompt (str): The prompt to be converted. + input_type (PromptDataType): The type of input data. Returns: ConverterResult: The result containing the generated version of the prompt. diff --git a/pyrit/prompt_converter/transparency_attack_converter.py b/pyrit/prompt_converter/transparency_attack_converter.py index bf11472fbc..9fbc7f8168 100644 --- a/pyrit/prompt_converter/transparency_attack_converter.py +++ b/pyrit/prompt_converter/transparency_attack_converter.py @@ -32,12 +32,12 @@ def __init__( self, *, learning_rate: float = 0.001, beta_1: float = 0.9, beta_2: float = 0.999, epsilon: float = 1e-8 ): """ - Initializes the Adam optimizer with specified hyperparameters. + Initialize the Adam optimizer with specified hyperparameters. Args: learning_rate (float): The step size for each update/iteration. Default is 0.001 - beta1 (float): The exponential decay rate for the first moment estimates. Default is 0.9 - beta2 (float): The exponential decay rate for the second moment estimates. Default is 0.999 + beta_1 (float): The exponential decay rate for the first moment estimates. Default is 0.9 + beta_2 (float): The exponential decay rate for the second moment estimates. Default is 0.999 epsilon (float): A small constant for numerical stability (to prevent division by zero). """ self.learning_rate = learning_rate @@ -50,7 +50,7 @@ def __init__( def update(self, *, params: numpy.ndarray, grads: numpy.ndarray) -> numpy.ndarray: """ - Performs a single update step using the Adam optimization algorithm. + Perform a single update step using the Adam optimization algorithm. Args: params (numpy.ndarray): Current parameter values to be optimized. @@ -107,7 +107,16 @@ class TransparencyAttackConverter(PromptConverter): @staticmethod def _validate_input_image(path: str) -> None: - """Validates input image to ensure it is a valid JPEG file.""" + """ + Validate input image to ensure it is a valid JPEG file. + + Args: + path (str): The file path to validate. + + Raises: + ValueError: If the path is empty or the file is not a JPEG. + FileNotFoundError: If the file does not exist at the specified path. + """ if not path: raise ValueError("The image path cannot be empty.") if not path.lower().endswith((".jpg", ".jpeg")): @@ -126,7 +135,7 @@ def __init__( convergence_patience: int = 10, ): """ - Initializes the converter with the path to a benign image and parameters for blending. + Initialize the converter with the path to a benign image and parameters for blending. Args: benign_image_path (Path): Path to the benign image file. Must be a JPEG file (.jpg or .jpeg). @@ -176,7 +185,18 @@ def __init__( self._cached_benign_image = self._load_and_preprocess_image(str(benign_image_path)) def _load_and_preprocess_image(self, path: str) -> numpy.ndarray: - """Loads image, converts to grayscale, resizes, and normalizes for optimization.""" + """ + Load image, convert to grayscale, resize, and normalize for optimization. + + Args: + path (str): The file path to the image. + + Returns: + numpy.ndarray: Preprocessed image as a normalized NumPy array. + + Raises: + ValueError: If the image cannot be loaded or processed. + """ try: with Image.open(path) as img: img_gray = img.convert("L") if img.mode != "L" else img # read as grayscale @@ -186,11 +206,29 @@ def _load_and_preprocess_image(self, path: str) -> numpy.ndarray: raise ValueError(f"Failed to load and preprocess image from {path}: {e}") def _compute_mse_loss(self, blended_image: numpy.ndarray, target_tensor: numpy.ndarray) -> float: - """Computes Mean Squared Error (MSE) loss between blended and target images.""" + """ + Compute Mean Squared Error (MSE) loss between blended and target images. + + Args: + blended_image (numpy.ndarray): The blended image array. + target_tensor (numpy.ndarray): The target benign image array. + + Returns: + float: The computed MSE loss value. + """ return float(numpy.mean(numpy.square(blended_image - target_tensor))) def _create_blended_image(self, attack_image: numpy.ndarray, alpha: numpy.ndarray) -> numpy.ndarray: - """Creates a blended image using the attack image and alpha transparency.""" + """ + Create a blended image using the attack image and alpha transparency. + + Args: + attack_image (numpy.ndarray): The attack image array. + alpha (numpy.ndarray): The alpha transparency array. + + Returns: + numpy.ndarray: The blended image in LA mode. + """ attack_image_uint8 = (attack_image * 255).astype(numpy.uint8) transparency_uint8 = (alpha * 255).astype(numpy.uint8) @@ -203,7 +241,19 @@ def _create_blended_image(self, attack_image: numpy.ndarray, alpha: numpy.ndarra return la_image async def _save_blended_image(self, attack_image: numpy.ndarray, alpha: numpy.ndarray) -> str: - """Saves the blended image with transparency as a PNG file.""" + """ + Save the blended image with transparency as a PNG file. + + Args: + attack_image (numpy.ndarray): The attack image array. + alpha (numpy.ndarray): The alpha transparency array. + + Returns: + str: The file path to the saved blended image. + + Raises: + ValueError: If saving the blended image fails. + """ try: img_serializer = data_serializer_factory(category="prompt-memory-entries", data_type="image_path") img_serializer.file_extension = "png" @@ -222,7 +272,7 @@ async def _save_blended_image(self, attack_image: numpy.ndarray, alpha: numpy.nd async def convert_async(self, *, prompt: str, input_type: PromptDataType = "image_path") -> ConverterResult: """ - Converts the given prompt by blending an attack image (potentially harmful) with a benign image. + Convert the given prompt by blending an attack image (potentially harmful) with a benign image. Uses the Novel Image Blending Algorithm from: https://arxiv.org/abs/2401.15817. Args: diff --git a/pyrit/prompt_converter/unicode_confusable_converter.py b/pyrit/prompt_converter/unicode_confusable_converter.py index ffdda42858..a6a275a18c 100644 --- a/pyrit/prompt_converter/unicode_confusable_converter.py +++ b/pyrit/prompt_converter/unicode_confusable_converter.py @@ -30,7 +30,7 @@ def __init__( deterministic: bool = False, ): """ - Initializes the converter with the specified source package for homoglyph generation. + Initialize the converter with the specified source package for homoglyph generation. Args: source_package (Literal["confusable_homoglyphs", "confusables"]): @@ -45,6 +45,9 @@ def __init__( Provides additional methods of matching characters (not just Unicode list), so each character has more possible substitutions. deterministic (bool): This argument is for unittesting only. + + Raises: + ValueError: If an invalid source package is provided. """ if source_package not in ["confusable_homoglyphs", "confusables"]: raise ValueError( @@ -56,7 +59,7 @@ def __init__( async def convert_async(self, *, prompt: str, input_type="text") -> ConverterResult: """ - Converts the given prompt by applying confusable substitutions. This leads to a prompt that looks similar, + Convert the given prompt by applying confusable substitutions. This leads to a prompt that looks similar, but is actually different (e.g., replacing a Latin 'a' with a Cyrillic 'а'). Args: @@ -81,7 +84,7 @@ async def convert_async(self, *, prompt: str, input_type="text") -> ConverterRes def _get_homoglyph_variants(self, word: str) -> list: """ - Retrieves homoglyph variants for a given word using the "confusable_homoglyphs" package. + Retrieve homoglyph variants for a given word using the "confusable_homoglyphs" package. Args: word (str): The word to find homoglyphs for. @@ -104,7 +107,7 @@ def _get_homoglyph_variants(self, word: str) -> list: def _generate_perturbed_prompts(self, prompt: str) -> str: """ - Generates a perturbed prompt by substituting characters with their homoglyph variants using the + Generate a perturbed prompt by substituting characters with their homoglyph variants using the "confusable_homoglyphs" package. Args: @@ -139,7 +142,7 @@ def _generate_perturbed_prompts(self, prompt: str) -> str: def _confusable(self, char: str) -> str: """ - Picks a confusable character for the given character using the "confusables" package. + Pick a confusable character for the given character using the "confusables" package. Args: char (str): The character to be replaced. diff --git a/pyrit/prompt_converter/unicode_replacement_converter.py b/pyrit/prompt_converter/unicode_replacement_converter.py index 440e8537a4..70a8442b79 100644 --- a/pyrit/prompt_converter/unicode_replacement_converter.py +++ b/pyrit/prompt_converter/unicode_replacement_converter.py @@ -19,7 +19,7 @@ def __init__( word_selection_strategy: Optional[WordSelectionStrategy] = None, ): """ - Initializes the converter with the specified selection strategy. + Initialize the converter with the specified selection strategy. Args: encode_spaces (bool): If True, spaces in the prompt will be replaced with unicode representation. @@ -30,9 +30,27 @@ def __init__( self.encode_spaces = encode_spaces async def convert_word_async(self, word: str) -> str: + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ return "".join(f"\\u{ord(ch):04x}" for ch in word) def join_words(self, words: list[str]) -> str: + """ + Join a list of words into a single string, optionally encoding spaces as unicode. + + Args: + words (list[str]): The list of words to join. + + Returns: + str: The joined string. + """ if self.encode_spaces: return "\\u0020".join(words) return super().join_words(words) diff --git a/pyrit/prompt_converter/unicode_sub_converter.py b/pyrit/prompt_converter/unicode_sub_converter.py index 7ab2919b71..a9da0b64b5 100644 --- a/pyrit/prompt_converter/unicode_sub_converter.py +++ b/pyrit/prompt_converter/unicode_sub_converter.py @@ -14,12 +14,28 @@ class UnicodeSubstitutionConverter(PromptConverter): SUPPORTED_OUTPUT_TYPES = ("text",) def __init__(self, *, start_value=0xE0000): + """ + Initialize the converter with a specified unicode starting point. + + Args: + start_value (int): The unicode starting point to use for encoding. + """ self.startValue = start_value async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by encoding it using any unicode starting point. + Convert the given prompt by encoding it using any unicode starting point. Default is to use invisible flag emoji characters. + + Args: + prompt (str): The prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the converted output and its type. + + Raises: + ValueError: If the input type is not supported. """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/url_converter.py b/pyrit/prompt_converter/url_converter.py index 973ea0e8d0..270ae8255c 100644 --- a/pyrit/prompt_converter/url_converter.py +++ b/pyrit/prompt_converter/url_converter.py @@ -17,7 +17,17 @@ class UrlConverter(PromptConverter): async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt into a URL-encoded string. + Convert the given prompt into a URL-encoded string. + + Args: + prompt (str): The prompt to be converted. + input_type (PromptDataType): The type of input data. + + Returns: + ConverterResult: The result containing the URL-encoded prompt. + + Raises: + ValueError: If the input type is not supported. """ if not self.input_supported(input_type): raise ValueError("Input type not supported") diff --git a/pyrit/prompt_converter/variation_converter.py b/pyrit/prompt_converter/variation_converter.py index 79000e6369..82451db017 100644 --- a/pyrit/prompt_converter/variation_converter.py +++ b/pyrit/prompt_converter/variation_converter.py @@ -43,7 +43,7 @@ def __init__( prompt_template: Optional[SeedPrompt] = None, ): """ - Initializes the converter with the specified target and prompt template. + Initialize the converter with the specified target and prompt template. Args: converter_target (PromptChatTarget): The target to which the prompt will be sent for conversion. @@ -69,10 +69,11 @@ def __init__( async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt by generating variations of it using the converter target. + Convert the given prompt by generating variations of it using the converter target. Args: prompt (str): The prompt to be converted. + input_type (PromptDataType): The type of input data. Returns: ConverterResult: The result containing the generated variations. @@ -119,8 +120,19 @@ async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text return ConverterResult(output_text=response_msg, output_type="text") @pyrit_json_retry - async def send_variation_prompt_async(self, request): - """Sends the message to the converter target and retrieves the response.""" + async def send_variation_prompt_async(self, request: Message) -> str: + """ + Send the message to the converter target and retrieve the response. + + Args: + request (Message): The message to be sent to the converter target. + + Returns: + str: The response message from the converter target. + + Raises: + InvalidJsonException: If the response is not valid JSON or does not contain the expected keys. + """ response = await self.converter_target.send_prompt_async(message=request) response_msg = response[0].get_value() diff --git a/pyrit/prompt_converter/word_level_converter.py b/pyrit/prompt_converter/word_level_converter.py index e9e645e4c1..2241ca005f 100644 --- a/pyrit/prompt_converter/word_level_converter.py +++ b/pyrit/prompt_converter/word_level_converter.py @@ -34,7 +34,7 @@ def __init__( word_split_separator: Optional[str] = " ", ): """ - Initializes the converter with the specified selection strategy. + Initialize the converter with the specified selection strategy. Args: word_selection_strategy (Optional[WordSelectionStrategy]): The strategy for selecting which @@ -49,7 +49,7 @@ def __init__( @abc.abstractmethod async def convert_word_async(self, word: str) -> str: """ - Converts a single word into the target format supported by the converter. + Convert a single word into the target format supported by the converter. Args: word (str): The word to be converted. @@ -60,16 +60,24 @@ async def convert_word_async(self, word: str) -> str: pass def validate_input(self, prompt: str) -> None: - """Validates the input before processing (can be overridden by subclasses).""" + """Validate the input before processing (can be overridden by subclasses).""" pass def join_words(self, words: list[str]) -> str: - """Provides a way for subclasses to override the default behavior of joining words.""" + """ + Provide a way for subclasses to override the default behavior of joining words. + + Args: + words (list[str]): List of words to join. + + Returns: + str: The joined string. + """ return " ".join(words) async def convert_async(self, *, prompt: str, input_type: PromptDataType = "text") -> ConverterResult: """ - Converts the given prompt into the target format supported by the converter. + Convert the given prompt into the target format supported by the converter. Args: prompt (str): The prompt to be converted. diff --git a/pyrit/prompt_converter/zalgo_converter.py b/pyrit/prompt_converter/zalgo_converter.py index 638b4af4cc..06a7abbf30 100644 --- a/pyrit/prompt_converter/zalgo_converter.py +++ b/pyrit/prompt_converter/zalgo_converter.py @@ -28,7 +28,7 @@ def __init__( word_selection_strategy: Optional[WordSelectionStrategy] = None, ): """ - Initializes the converter with the specified selection parameters. + Initialize the converter with the specified selection parameters. Args: intensity (int): Number of combining marks per character (higher = more cursed). Default is 10. @@ -55,6 +55,15 @@ def _normalize_intensity(self, intensity: int) -> int: return normalized_intensity async def convert_word_async(self, word: str) -> str: + """ + Convert a single word into the target format supported by the converter. + + Args: + word (str): The word to be converted. + + Returns: + str: The converted word. + """ if self._intensity <= 0: return word @@ -64,6 +73,7 @@ def glitch(char: str) -> str: return "".join(glitch(c) if c.isalnum() else c for c in word) def validate_input(self, prompt: str) -> None: + """Validate the input prompt before conversion.""" # Initialize the random seed before processing any words if self._seed is not None: random.seed(self._seed) From f1296f79fe17b09eb8d0e0d4f55344b008b31fa3 Mon Sep 17 00:00:00 2001 From: Paulina Kalicka <71526180+paulinek13@users.noreply.github.com> Date: Tue, 13 Jan 2026 21:48:36 +0100 Subject: [PATCH 3/3] precommit --- pyrit/prompt_converter/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrit/prompt_converter/__init__.py b/pyrit/prompt_converter/__init__.py index 04ef131f70..692a458e85 100644 --- a/pyrit/prompt_converter/__init__.py +++ b/pyrit/prompt_converter/__init__.py @@ -4,10 +4,10 @@ """ Prompt converters for transforming prompts before sending them to targets in red teaming workflows. -Converters are organized into categories: Text-to-Text (encoding, obfuscation, translation, variation), -Audio (text-to-audio, audio-to-text, audio-to-audio), Image (text-to-image, image-to-image), -Video (image-to-video), File (text-to-PDF/URL), Selective Converting (partial prompt transformation), -and Human-in-the-Loop (interactive review). Converters can be stacked together to create complex +Converters are organized into categories: Text-to-Text (encoding, obfuscation, translation, variation), +Audio (text-to-audio, audio-to-text, audio-to-audio), Image (text-to-image, image-to-image), +Video (image-to-video), File (text-to-PDF/URL), Selective Converting (partial prompt transformation), +and Human-in-the-Loop (interactive review). Converters can be stacked together to create complex transformation pipelines for testing AI system robustness. """