Aller au contenu principal

Architecture de sécurité complète

Mis à jour le 29 juillet 2026

Assembler toutes les pièces

Au fil de cette formation, vous avez découvert les guardrails, l’API de modération, les 11 catégories, les seuils, la détection de jailbreak, la conformité RGPD et les politiques de contenu. Cette dernière leçon assemble toutes ces briques dans une architecture de sécurité complète, prête pour la production.

Vue d’ensemble de l’architecture

Cinq couches, et un principe qui justifie leur nombre : aucune n’est fiable seule. Un guardrail se contourne par une formulation inattendue, un modèle de modération a ses faux négatifs, une politique de contenu ne couvre jamais tous les cas. L’empilement fonctionne parce que les failles ne se superposent pas — ce qui passe la première couche a de bonnes chances d’être arrêté par la deuxième.

La conséquence pratique est un ordre à respecter : chaque couche doit être la moins chère possible avant celle qui la suit. On valide le format avant d’appeler un guardrail, on appelle le guardrail avant de générer — car un contenu refusé à l’étape 1 n’aura consommé aucun token.

Utilisateur

[1. Validation d'entrée] → Regex, patterns, rate limiting

[2. Guardrails Mistral]  → Blocage pré-génération (403)

[3. Génération LLM]      → safe_prompt + instructions sécurisées

[4. Modération sortie]   → API Moderation post-génération

[5. Logging & Audit]     → Scores, décisions, audit trail RGPD

Réponse utilisateur

Implémentation complète

from mistralai import Mistral
from mistralai.models import SDKError
import os
import re
import json
import logging
from datetime import datetime
from enum import Enum

# Configuration
client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("moderation")


class Decision(Enum):
    ALLOWED = "allowed"
    BLOCKED_INPUT = "blocked_input"
    BLOCKED_GUARDRAIL = "blocked_guardrail"
    BLOCKED_OUTPUT = "blocked_output"
    ERROR = "error"


class ArchitectureSecurisee:
    """Architecture de sécurité complète pour applications IA."""

    def __init__(self, politique: dict, model: str = "mistral-large-latest"):
        self.politique = politique
        self.model = model
        self.stats = {d.value: 0 for d in Decision}

    # --- Couche 1 : Validation d'entrée ---

    def valider_entree(self, texte: str) -> dict:
        """Validation rapide avant toute API."""
        # Longueur maximale
        if len(texte) > 10000:
            return {"valide": False, "raison": "Message trop long"}

        # Caractères suspects (injections Unicode)
        if re.search(r"[\x00-\x08\x0b\x0c\x0e-\x1f]", texte):
            return {"valide": False, "raison": "Caractères invalides"}

        # Patterns de jailbreak évidents
        patterns = [
            r"ignore\s+(all\s+)?(previous|prior|above)",
            r"you\s+are\s+now\s+(DAN|unfiltered)",
            r"oublie\s+(toutes?\s+)?(tes|vos)\s+instructions",
            r"system\s*prompt",
        ]
        for pattern in patterns:
            if re.search(pattern, texte, re.IGNORECASE):
                return {
                    "valide": False,
                    "raison": "Pattern injection détecté"
                }

        return {"valide": True}

    # --- Couche 2 : Modération entrée ---

    def moderer_entree(self, texte: str) -> dict:
        """Modération du prompt via API."""
        response = client.classifiers.moderate(
            model="mistral-moderation-2603",
            inputs=[texte]
        )
        scores = response.results[0].category_scores
        violations = self._verifier_seuils(scores)

        return {
            "autorise": len(violations) == 0,
            "scores": scores,
            "violations": violations
        }

    # --- Couche 3 : Génération sécurisée ---

    def generer(self, messages: list) -> str:
        """Génération avec guardrails et safe_prompt."""
        response = client.chat.complete(
            model=self.model,
            messages=messages,
            safe_prompt=True,
            guardrails={
                "enabled": True,
                "custom_category_thresholds":
                    self.politique["seuils"],
                "action": "block",
                "block_on_error": True
            }
        )
        return response.choices[0].message.content

    # --- Couche 4 : Modération sortie ---

    def moderer_sortie(self, prompt: str, reponse: str) -> dict:
        """Modération de la réponse en contexte."""
        response = client.classifiers.moderate_chat(
            model="mistral-moderation-2603",
            inputs=[[
                {"role": "user", "content": prompt},
                {"role": "assistant", "content": reponse}
            ]]
        )
        scores = response.results[0].category_scores
        violations = self._verifier_seuils(scores)

        return {
            "autorise": len(violations) == 0,
            "scores": scores,
            "violations": violations
        }

    # --- Couche 5 : Logging ---

    def log_decision(self, decision: Decision, data: dict):
        """Enregistre la décision pour audit."""
        self.stats[decision.value] += 1
        entry = {
            "timestamp": datetime.utcnow().isoformat(),
            "decision": decision.value,
            "politique": self.politique["nom"],
            **data
        }
        logger.info(json.dumps(entry, ensure_ascii=False))

    # --- Pipeline principal ---

    def traiter(self, user_message: str,
                system_prompt: str = None) -> dict:
        """Pipeline complet de traitement sécurisé."""

        # Couche 1 : Validation
        validation = self.valider_entree(user_message)
        if not validation["valide"]:
            self.log_decision(Decision.BLOCKED_INPUT, {
                "raison": validation["raison"]
            })
            return {
                "status": Decision.BLOCKED_INPUT.value,
                "message": "Votre message ne peut pas être traité."
            }

        # Couche 2 : Modération entrée
        check_input = self.moderer_entree(user_message)
        if not check_input["autorise"]:
            self.log_decision(Decision.BLOCKED_INPUT, {
                "violations": check_input["violations"]
            })
            return {
                "status": Decision.BLOCKED_INPUT.value,
                "message": "Ce contenu enfreint notre politique."
            }

        # Couche 3 : Génération
        messages = []
        if system_prompt:
            messages.append({"role": "system",
                           "content": system_prompt})
        messages.append({"role": "user",
                        "content": user_message})

        try:
            reponse = self.generer(messages)
        except SDKError as e:
            if e.status_code == 403:
                self.log_decision(Decision.BLOCKED_GUARDRAIL, {})
                return {
                    "status": Decision.BLOCKED_GUARDRAIL.value,
                    "message": "Requête bloquée par les guardrails."
                }
            raise

        # Couche 4 : Modération sortie
        check_output = self.moderer_sortie(user_message, reponse)
        if not check_output["autorise"]:
            self.log_decision(Decision.BLOCKED_OUTPUT, {
                "violations": check_output["violations"]
            })
            return {
                "status": Decision.BLOCKED_OUTPUT.value,
                "message": "La réponse a été filtrée."
            }

        # Succès
        self.log_decision(Decision.ALLOWED, {
            "input_max_score": max(
                check_input["scores"].values()
            ),
            "output_max_score": max(
                check_output["scores"].values()
            )
        })

        return {
            "status": Decision.ALLOWED.value,
            "content": reponse,
            "moderation": {
                "input": check_input["scores"],
                "output": check_output["scores"]
            }
        }

    # --- Utilitaires ---

    def _verifier_seuils(self, scores: dict) -> list:
        """Vérifie les scores contre les seuils de la politique."""
        violations = []
        for cat, seuil in self.politique["seuils"].items():
            score = scores.get(cat, 0)
            if score > seuil:
                violations.append({
                    "categorie": cat,
                    "score": round(score, 3),
                    "seuil": seuil
                })
        return violations

    def rapport(self) -> dict:
        """Génère un rapport de statistiques."""
        total = sum(self.stats.values())
        return {
            "politique": self.politique["nom"],
            "total_requetes": total,
            "decisions": self.stats,
            "taux_blocage": round(
                (self.stats["blocked_input"] +
                 self.stats["blocked_guardrail"] +
                 self.stats["blocked_output"]) / max(total, 1),
                3
            )
        }

Utilisation en production

# Définir la politique
politique_prod = {
    "nom": "Production v1.2",
    "seuils": {
        "sexual": 0.15, "hate": 0.1, "violence": 0.2,
        "criminal": 0.1, "selfharm": 0.1, "health": 0.4,
        "financial": 0.4, "law": 0.4, "pii": 0.15,
        "jailbreaking": 0.1, "unpredictable": 0.3
    }
}

# Instancier l'architecture
archi = ArchitectureSecurisee(politique_prod)

# Traiter des requêtes
resultat = archi.traiter(
    "Expliquez-moi le fonctionnement des transformers",
    system_prompt="Vous êtes un formateur en IA."
)
print(resultat["status"])   # → "allowed"
print(resultat["content"])  # → Explication des transformers

# Consulter les statistiques
print(archi.rapport())

Monitoring et alertes

Deux métriques suffisent à piloter un système de modération, et elles se surveillent dans deux directions opposées. Le taux de blocage doit rester stable : une hausse soudaine signale soit une attaque, soit un seuil trop strict qui commence à gêner des utilisateurs légitimes. La distribution des scores compte tout autant — si vos contenus se concentrent juste sous le seuil, c’est que celui-ci est mal placé, et le prochain ajustement de vos utilisateurs le fera sauter.

def configurer_alertes(archi: ArchitectureSecurisee):
    """Configure les alertes pour le monitoring."""
    rapport = archi.rapport()

    # Alerte si taux de blocage anormal
    if rapport["taux_blocage"] > 0.15:
        logger.warning(
            f"Taux de blocage élevé : "
            f"{rapport['taux_blocage']:.1%}"
        )

    # Alerte si pics de jailbreak
    # Intégrez avec Prometheus, Grafana, Datadog, etc.

Récapitulatif de la formation

Au terme de cette formation, vous maîtrisez :

  • Custom Guardrails : bloquer les prompts dangereux avant la génération, configurer les seuils par catégorie, appliquer aux agents
  • API Moderation : classifier le contenu avec Mistral Moderation 2, interpréter les scores des 11 catégories, prendre des décisions automatisées
  • Sécurité en production : définir des politiques de contenu, détecter les prompt injections, assurer la conformité RGPD
  • Architecture complète : assembler guardrails + modération + monitoring dans un pipeline robuste

Vous disposez de tout le code nécessaire pour déployer un système de modération IA complet avec l’API Mistral.

Points clés à retenir

  • L’architecture de sécurité comprend 5 couches : validation, guardrails, génération, modération, logging
  • Chaque couche compense les faiblesses des autres — c’est la défense en profondeur
  • Monitorez le taux de blocage et les scores en production
  • Maintenez votre politique à jour avec des révisions régulières
  • Le code complet de cette leçon est un point de départ — adaptez-le à votre contexte

Testez vos connaissances

Guardrails, modération, injection : l’architecture défensive est-elle claire ?

1. Où interviennent les custom guardrails ?

Réponse : Avant le modèle : ils filtrent les entrées selon vos règles (sujets interdits, formats) — la première couche, qui évite même l’appel quand la demande est hors périmètre.

2. Que couvre l'API Moderation de Mistral ?

Réponse : Onze catégories de contenus à risque, avec un score par catégorie : vos seuils décident de bloquer, signaler ou laisser passer — la politique de contenu devient du code.

3. Comment se définit une bonne politique de contenu ?

Réponse : Par cas d’usage : ce qui est acceptable pour l’application, les seuils par catégorie, les actions associées (blocage, reformulation, escalade) — documentée et testée, pas implicite.

4. Comment détecter les tentatives de prompt injection ?

Réponse : En traitant tout contenu externe comme suspect : détection de motifs d’instruction dans les données, cloisonnement instructions/données, validation des sorties — et tests offensifs réguliers.

5. À quoi ressemble l'architecture de sécurité complète ?

Réponse : Des couches : guardrails d’entrée, modération des contenus, contrôles de sortie, conformité RGPD (minimisation, droits) et traçabilité — aucune couche n’est parfaite, l’empilement les compense.

La défense en profondeur n’est pas un slogan : chaque couche du cours attrape ce que la précédente laisse passer.