Red Teaming LLM - Guide d'utilisation de Co-PyRIT

Guide pratique de l'interface web Co-PyRIT, l'outil open source de Microsoft pour le red teaming de LLM : installation, exécution et interprétation des résultats.

Read this article in English

Si vous êtes sur cette page, vous avez sûrement déjà passé en revue l’article précédent sur le scanner CLI de PyRIT ou avez déjà des bases sur cet outil. Si ce n’est pas le cas, il est vivement conseillé de consulter l’article précédent du scanner CLI afin de bien vous imprégner des différents composants.

Dans cet article, nous allons passer en revue l’interface web, Co-PyRIT. Cet outil fait bien partie du même projet PyRIT qui est actuellement développé et maintenu par Microsoft. L’outil est open-source et son code est disponible via le site web officiel de PyRIT.

Commençons par découvrir l’interface et à comprendre comment elle permet d’exécuter des scénarios et d’interagir avec un modèle cible d’une manière plus accessible pour l’utilisateur.

Remarque : Dans le cas où vous souhaiteriez suggérer une amélioration ou simplement me contacter, vous pouvez le faire à contact@lionsec.lu.

Présentation

Co-PyRIT, bien qu’en développement actif, permet déjà de nombreuses fonctionnalités telles que :

  • L’interaction directe avec le modèle cible via une interface
  • Utilisation embarquée de 95 converters (Base64, LeetSpeak, ROT13 etc…)
  • Suivi de l’historique des conversations d’attaque
  • Export des conversations
  • Etc …

Ce projet permet la même utilisation que le scanner CLI en version plus approchable, et peut d’ailleurs même regrouper les résultats de ces deux outils en même temps. Nous allons voir que certaines fonctionnalités sont assez floues pour le moment car le projet est en développement, donc nous allons tenter de répondre à ces subtilités du mieux possible.

Installation

Son utilisation nécessite l’installation de PyRIT de base, nous reprenons ici les mêmes instructions que dans l’article précédent.

Pour installer PyRIT, nous partons d’une distribution Kali avec VirtualBox et saisissons les commandes suivantes dans un terminal :

Bash
python3 -m venv ~/pyrit-venv
source ~/pyrit-venv/bin/activate
python -m pip install pyrit

On affiche le numéro de version pour s’assurer que tout s’est bien déroulé :

Bash
python -c "import pyrit; print(f'PyRIT version installed: {pyrit.__version__}')"

Le numéro de version doit s’afficher (v1.1.0 durant la rédaction de cet article).

Configuration

De même, nous devons configurer l’outil, tout comme pour le scanner CLI. Une fois installé, nous allons configurer PyRIT afin qu’il puisse charger les composants dont vous aurez besoin, tels que Target, Technique, Scorer et Memory, ainsi que vos informations sur le modèle cible.

Initialisation des composants

Pour faire très simple, PyRIT utilise des registres afin de gérer centralement ses différents composants dont le scanner a besoin car il utilise une technique de chargement de lazy loading pour ne pas tous les charger d’un coup. Ce choix a été fait car le chargement de certains composants pourrait prendre du temps. Pour charger les composants, le fichier ~/.pyrit/.pyrit_conf doit comporter la configuration recommandée (selon la documentation officielle de PyRIT) :

Yaml
memory_db_type: in_memory

initializers:
  - name: target
    args:
      tags:
        - default
        - scorer
  - name: scorer
  - name: technique

Si vous souhaitez configurer une URL et un port différents, vous pouvez le faire en rajoutant les directives suivantes à la fin du fichier de configuration :

Yaml
server:
  url: http://localhost:8081
  startup_timeout: 120
  auth_mode: none

Initialisation de la cible

Ensuite, pour la configuration de votre cible, il y a deux manières de procéder :

Remarque : Il est à noter que PyRIT supporte différents types de cibles via des Targets.

Soit par le fichier ~/.pyrit/.env :

Ini
OPENAI_CHAT_ENDPOINT="https://<provider_url>"
OPENAI_CHAT_KEY="sk-proj-*****"
OPENAI_CHAT_MODEL="gpt-4o-mini"

Soit en exportant les informations dans le terminal :

Bash
export OPENAI_CHAT_ENDPOINT="https://<provider_url>"
export OPENAI_CHAT_KEY="sk-proj-*****"
export OPENAI_CHAT_MODEL="gpt-4o-mini"

Exécution du serveur back-end

Une fois installé et configuré, nous pouvons désormais exécuter le serveur back-end comme ceci :

Bash
pyrit_scan start-server

De même, pour l’arrêter :

Bash
pyrit_scan stop-server

Dans l’éventualité où vous auriez plusieurs configurations ou un besoin de déboguer, vous pouvez utiliser les paramètres suivants pour spécifier le fichier de configuration et/ou l’URL du serveur back-end :

Bash
pyrit_scan start-server --config-file "$HOME/.pyrit/.pyrit_conf" --server-url http://localhost:8081

Voilà, nous pouvons désormais accéder à l’interface web.

L’interface web

Une fois le serveur back-end en cours d’exécution, vous pouvez vous rendre à son adresse (par exemple : http://localhost:8000) et vous ferez face à cette page d’accueil :

Page d'accueil Co-PyRIT

Celle-ci nous propose rapidement de définir des labels, de configurer une cible ou même de discuter directement avec une cible. Commençons par configurer notre cible.

Configurer une cible

Avant d’interagir avec quoi que ce soit, nous devons définir une cible à attaquer : soit en cliquant sur le bouton Configure a target de la page d’accueil, soit en allant dans le menu Target dans la barre de menu à gauche : Liste de modèles cibles

La page suivante s’affiche :

Configuration d'un modèle cible

Vous pouvez déjà voir une cible, celle que vous avez ajoutée au fichier .env. Si vous souhaitez l’utiliser, vous avez simplement à cliquer sur le bouton “Set Active”. Sinon, vous pouvez en créer une via le bouton “New Target” en haut à droite.

Remarque : Dans le cas où vous voudriez créer une nouvelle cible, Co-PyRIT n’intègre pas tous les types de cibles possibles (v1.1.0). La documentation contient la liste exhaustive de toutes celles qui sont supportées. Dans tous les cas, les modèles suivants (étant compatibles avec l’API OpenAI) sont supportés via le type OpenAIChatTarget (OpenAI, Azure OpenAI, Ollama (Local), Groq, HuggingFace et OpenRouter).

Remarque 2 : Aussi, les cibles créées via le bouton New Target ne persisteront pas une fois le serveur arrêté car elles sont préservées en mémoire (in_memory). Pour les faire persister, il faut les ajouter dans le .env.

Une fois ajoutée, nous retrouvons les informations de la cible ainsi qu’une liste d’attributs :

Attribut Description
Inputs Types de données que la cible accepte (texte, audio, image)
Outputs Type de données que la cible retourne (texte)
Multi-turn Peut maintenir un historique de conversation
Multi-piece Peut recevoir plusieurs parties dans un seul message (texte + image)
JSON Schema Peut recevoir votre schéma JSON pour structurer les réponses selon vos préférences
JSON Output Peut retourner un résultat au format JSON
Edit History L’historique de conversation peut être changé
System Prompt Accepte les messages de rôle system
Parameters Réglages divers de la cible (par exemple : endpoint, model, temperature, max_tokens etc …)

Co-PyRIT indique ces informations en se basant sur ce qu’il connaît déjà de la cible et n’effectue pas de vérification directe avec la cible en envoyant une requête. En revanche, cela est possible avec le framework.

Une fois la cible définie et activée, vous allez pouvoir commencer à discuter avec !

Discuter avec la cible

Vous l’avez probablement déjà repéré, le menu Chat dans la barre de menu à gauche. Sans avoir défini de cible active, un message d’erreur s’affiche mais comme nous venons de le faire, nous allons pouvoir discuter directement.

Envoi de message à la cible

Sur cette interface, rien de compliqué : nous pouvons juste observer différentes fonctionnalités intéressantes telles que :

  • L’envoi possible de fichiers (lorsque la cible est multimodale)
  • L’export des conversations aux formats HTML, Markdown et JSON
  • Le rendu des prompts au format Markdown (agit uniquement côté client pour une question de rendu)
  • La possibilité d’utiliser des converters

Cette dernière fonctionnalité est celle dont nous allons traiter dans le paragraphe suivant, car elle permet énormément de choses, en revanche, il est possible de rencontrer certaines difficultés auxquelles nous allons répondre.

Utiliser les converters

Comme évoqué plus haut, PyRIT embarque 95 converters. Deux types s’offrent à nous : des converters statiques (le code s’exécute sur votre machine) et les converters nécessitant un LLM pour fonctionner.

Par exemple, les encodages de type Base64, ROT13 ou même LeetSpeak. Du code afin de générer du texte dans ces formats existe depuis très longtemps. En revanche, des encodages tels que MathPrompt, Persuasion ou Tone reposent sur le message qui vous vous apprêtez à envoyer à votre cible. Ils sont dynamiques et donc nécessitent l’usage d’un LLM.

Remarque : Il est recommandé d’ajouter la directive name: load_default_datasets à votre fichier ~/.pyrit/.pyrit_conf afin de rendre disponibles tous les datasets nécessaires aux scénarios.

Seulement, une erreur survient lorsque nous tentons d’utiliser l’un de ces converters LLM : Vous saisissez le texte que vous souhaiteriez envoyer, choisissez le MathPromptConverter pour transformer votre prompt en problème mathématique, vous cliquez ensuite sur le bouton Preview et là … erreur.

Erreur du converter Math

Comme ce converter nécessite un LLM, Co-PyRIT est simplement en train de vous dire qu’il a besoin d’un LLM pour générer la payload. Pour cela, vous devez créer un initializer custom pour indiquer à PyRIT le LLM que vous souhaitez utiliser. Pour ce faire, voici un script à créer (par exemple : ~/.pyrit/set_converter_target_default.py) :

Python
from pyrit.converter import DecompositionConverter, MathPromptConverter, ToneConverter
from pyrit.common.apply_defaults import set_default_value
from pyrit.registry import TargetRegistry
from pyrit.setup.pyrit_initializer import PyRITInitializer

class RegisterLLMConvertersInitializer(PyRITInitializer):
    @property
    def name(self) -> str:
        return "register_llm_converters"

    async def initialize_async(self) -> None:
        targets = TargetRegistry.get_registry_singleton()
        # Use any target name listed by the CLI scanner command 'list-targets'.
        # Here we use the same model as a target and for converters.
        # You could configure another one in .env just for converters.
        try:
            llm = targets.instances.get("openai_chat")
        except Exception:
            names = list(getattr(targets.instances, "get_names", lambda: [])())
            raise RuntimeError(
                f"No openai_chat in TargetRegistry. Available: {names}. "
                "Fix OPENAI_CHAT_* and run the target initializer first."
            )

        set_default_value(
            class_type=DecompositionConverter,
            parameter_name="converter_target",
            value=llm,
            include_subclasses=True,
        )

        set_default_value(
            class_type=ToneConverter,
            parameter_name="converter_target",
            value=llm,
            include_subclasses=True,
        )

        set_default_value(
            class_type=MathPromptConverter,
            parameter_name="converter_target",
            value=llm,
            include_subclasses=True,
        )

En bref, ce script va créer un Initializer (qui hérite de la classe PyRITInitializer) que l’on pourra déclarer dans le fichier ~/.pyrit/.pyrit_conf. Il va ensuite lister toutes les targets que nous avons configurées pour ensuite définir le fameux converter_target que nous avions dans le message d’erreur ci-dessus (via set_default_value()) ainsi que la classe du converter concerné.

Remarque : Vous pouvez rajouter un set_default_value() pour chaque converter qui retourne une erreur. Ne pas oublier d’importer sa classe tout en haut ! Si une erreur de syntaxe dans votre script est détectée, plus aucun converter ne fonctionnera dans Co-PyRIT.

Pour prendre en compte cet Initializer custom, rajoutez la directive suivante à la fin de ~/.pyrit/.pyrit_conf :

Yaml
initialization_scripts:
  - /home/<USER>/.pyrit/set_converter_target_default.py

Il ne reste plus qu’à redémarrer le serveur pour prendre en compte les changements :

Bash
pyrit_scan stop-server && pyrit_scan start-server

Remarque : Non, il n’y a pas de commande restart pour le moment.

Nous pouvons relancer l’interface, réactiver notre cible et tester. Nous avons maintenant le converter MathPromptConverter complètement fonctionnel :

Bug converter Math corrigé

Libre à vous d’explorer les nombreuses possibilités car il y a de quoi faire, sachant que le framework peut en enchaîner plusieurs contrairement à Co-PyRIT !

Scanner

Dans le menu Scanner à gauche, vous pourrez y retrouver tous les scénarios prédéfinis (les mêmes qu’avec le scanner CLI). Chacun d’entre eux peut être configuré.

Liste des scénarios

Nous allons utiliser le scénario airt.psychosocial comme évoqué dans l’article précédent du scanner CLI. La page de configuration nous donne immédiatement accès à la liste des techniques disponibles (avec celles par défaut déjà sélectionnées) :

Détails du scénario airt.psychosocial

La prochaine section concerne les paramètres du scénario, dont l’ensemble des valeurs est trouvable via la commande list-scenarios avec le scanner CLI. Tous les paramètres ne sont pas les mêmes selon le scénario mais on retrouve souvent les mêmes :

Paramètres du scénario airt.psychosocial

Enfin, la dernière section définit des estimations, notamment du nombre d’attaques atomiques :

Estimations du scénario

Remarque : Une attaque atomique n’est pas l’équivalent d’un message envoyé au modèle cible (et donc 1 requête). Elle correspond à l’ensemble d’une technique et d’une entrée du dataset. Une attaque single-turn ne génèrera qu’une seule requête, en revanche, une multi-turn (avec un max turn de 5) pourrait en générer jusqu’à 5 car elle peut s’arrêter avant en cas de succès.

En configurant le scénario, nous voyons certaines données comme exemple pour certains champs. Ici, nous voyons les propositions cyber et violence pour l’entrée Harm categories, or, si nous saisissons manuellement cyber, nous verrons une erreur s’afficher en bas de la page en évoquant que le dataset ne possède pas cette catégorie. Assez paradoxal, non ?!

Erreur de catégories

Pour cela, on peut soit exécuter le test sans définir de valeur (il prendra la valeur par défaut), soit on peut aller la chercher dans son fichier .prompt sur le GitHub officiel de PyRIT.

Une fois que tout est configuré, le test est prêt à être lancé, et l’historique nous permettra de le suivre à la trace.

Historique

Ce menu est très rapide à passer en revue. Il permet la distinction des conversations d’attaque à travers plusieurs filtres (résultat final, type d’attaque, converters etc …), mais aussi entre Co-PyRIT ou le scanner CLI (ils partagent le même serveur back-end donc tous les résultats y sont regroupés. Utile pendant un audit lorsque le scanner tourne à plein régime pendant que vous essayez vos attaques favorites à travers la fenêtre de chat).

Cliquer sur une entrée de l’historique vous ramène simplement à la conversation.

Historique des conversations d'attaques

Félicitations, vous êtes arrivé au dernier menu de Co-PyRIT présenté dans cet article !

Conclusion

Co-PyRIT propose une interface web plus user-friendly que le scanner CLI et est en développement actif par Microsoft. La documentation est assez limitée, et son utilisation peut amener à certaines subtilités que j’espère avoir pu couvrir dans cet article. En attendant, la possibilité d’utiliser les converters directement dans le menu de conversation ainsi que de suivre les historiques du scanner CLI également sont des fonctionnalités très appréciables. Je pense que nous avons tous hâte de voir ce projet évoluer avec le temps.

Il est désormais temps de passer en revue le dernier composant : le framework en Python afin d’exposer toute la puissance de PyRIT.

Liens

  1. GitHub officiel de PyRIT
  2. Site web officiel de PyRIT
  3. Image VirtualBox de Kali
  4. Guide d’utilisation du Scanner PyRIT
  5. Guide d’utilisation du framework PyRIT

Contact

Pour toute suggestion d’amélioration ou simplement me contacter : contact@lionsec.lu.