OWASP Top 10 LLM : du référentiel aux pipelines MLSecOps
← Retour au blogSécurité

OWASP Top 10 LLM : du référentiel aux pipelines MLSecOps

31 août 20268 min de lectureOWASPLLMMLSecOps

Le Top 10 OWASP pour applications LLM décodé risque par risque, et comment le traduire en pipelines MLSecOps concrets : signature de modèles, red teaming en CI, admission control et garde-fous runtime.

Pourquoi un Top 10 spécifique aux LLM ?

Le Top 10 OWASP historique repose sur une hypothèse simple : les instructions et les données circulent sur des canaux distincts. Le code est du code, l'entrée utilisateur est une chaîne que l'on valide, échappe et paramètre. Les grands modèles de langage cassent cette hypothèse au niveau architectural. Prompt système, document récupéré par le RAG, résultat d'un outil et question de l'utilisateur arrivent dans un seul flux de tokens. Le modèle n'a aucun moyen structurel de distinguer ce qui fait autorité.

C'est la raison d'être du OWASP Top 10 for LLM Applications, publié par le GenAI Security Project en 2023 puis largement remanié en 2025. Cette révision mérite une lecture attentive : certaines entrées de 2023 ont disparu (le vol de modèle, la sur-confiance comme item autonome) au profit de risques devenus visibles seulement après la mise en production de vraies chaînes RAG et de vrais agents — fuite du prompt système, faiblesses des bases vectorielles, consommation non bornée.

La liste 2025 et le contrôle qui compte vraiment

IDRisqueÀ quoi ça ressemble en prodContrôle à plus fort levier
LLM01Prompt InjectionUne page web indexée ou un ticket de support porte des instructions cachées que l'agent exécuteOutils au moindre privilège, confirmation humaine sur les effets de bord, frontières de confiance par source
LLM02Divulgation d'informations sensiblesSecrets ou données d'un autre tenant qui ressortent dans une complétionRédaction avant inference, retrieval sensible aux permissions, scan des sorties
LLM03Chaîne d'approvisionnementPoids non épinglés, pickle malveillant, adaptateur LoRA compromisRegistre interne, vérification de signature, politique safetensors uniquement
LLM04Empoisonnement des données et du modèleCorpus de fine-tuning pollué, backdoor dans un adaptateurProvenance des jeux de données, traçabilité du lineage, évaluations anti-backdoor
LLM05Traitement non sûr des sortiesSortie du modèle exécutée en SQL, shell, HTML ou lien MarkdownToute complétion est une entrée non fiable : encoder, valider, allowlister
LLM06Agentivité excessiveAgent disposant d'un accès en écriture au CRM, à une boîte mail et à un clusterJetons cloisonnés par outil, pas de verbes destructifs, workflow d'approbation
LLM07Fuite du prompt systèmeExtraction du prompt révélant règles métier et endpoints internesAucun secret ni logique d'autorisation dans le prompt
LLM08Faiblesses vecteurs et embeddingsFuite inter-tenants dans une collection partagée, inversion d'embeddingIsolation par tenant, filtres appliqués à la requête, réindexation sur changement d'ACL
LLM09DésinformationHallucinations assurées consommées par un process automatisé en avalAncrage avec citations, seuils de confiance, revue humaine sur les cas à fort impact
LLM10Consommation non bornéeBoucles d'agent récursives qui brûlent le budget tokens d'un trimestre en un week-endBudgets de tokens par tenant, limites dures d'étapes, alerting sur la dépense

La prompt injection n'est pas un bug que l'on corrige

L'erreur la plus répandue consiste à traiter LLM01 comme un problème de filtrage. On branche un classifieur de jailbreak, on constate qu'il bloque les ignore previous instructions les plus naïfs, et on considère le sujet clos. Puis quelqu'un dissimule des instructions dans un PDF, un commentaire Jira, un commentaire HTML d'une page crawlée ou une chaîne base64 au milieu d'un bloc de code — et l'agent exfiltre gaiement le contexte via l'URL d'une image Markdown.

L'injection indirecte est un problème d'architecture. La bonne question n'est pas « comment empêcher l'injection ? » mais « que se passe-t-il de pire quand elle réussit ? ». Si la réponse est « l'agent supprime des enregistrements de production » ou « l'agent envoie un mail contenant le contexte récupéré vers une adresse contrôlée par l'attaquant », aucun filtre ne vous sauvera. Il faut concevoir comme on conçoit face à un pod compromis : périmètre minimal, pas de credentials ambiants, allowlist de sortie réseau, et actions irréversibles derrière un humain ou une politique signée.

Sorties et agentivité : là où les incidents se produisent réellement

LLM05 et LLM06 se renforcent mutuellement. Une complétion exécutée sans validation, combinée à un agent porteur de droits larges, c'est l'équivalent GenAI d'une application web tournant en root avec du SQL concaténé.

# Anti-pattern : la sortie du modèle est exécutée telle quelle
sql = llm.invoke(f"Traduis en SQL : {question}")
db.execute(sql)   # une injection indirecte suffit à tout extraire

# Pattern : intention structurée + allowlist + autorisation côté serveur
intent = llm.with_structured_output(QueryIntent).invoke(question)
if intent.table not in TABLES_AUTORISEES:
    raise PermissionError(intent.table)
rows = repo.select(
    table=intent.table,
    filters=intent.filters,          # typés et validés
    tenant_id=session.tenant_id,     # jamais fourni par le modèle
    limit=min(intent.limit, 500),
)

La règle tient en une phrase : le modèle choisit l'intention, votre code détient l'autorité. Tout ce qu'émet le modèle — SQL, chemin de fichier, nom d'outil, URL, HTML — est une entrée contrôlée par l'utilisateur et mérite le même traitement qu'un paramètre de requête.

RAG : le maillon faible s'appelle souvent la base vectorielle

LLM08 est l'entrée qui surprend le plus les équipes. Une base vectorielle contenant les embeddings de tout votre Confluence, de votre CRM et du drive RH est de fait une copie secondaire de vos données — sans aucune des ACL d'origine. Deux modes de défaillance dominent.

D'abord le post-filtrage : récupérer le top-k puis écarter les chunks que l'utilisateur n'a pas le droit de voir fuit par les signaux de pertinence et renvoie un contexte pauvre et incohérent. Les filtres doivent s'appliquer au moment de la requête, à partir de l'identité de l'appelant et de métadonnées écrites à l'ingestion. Ensuite les ACL périmées : les droits changent dans le système source, personne ne réindexe, et un document reste lisible dans le RAG longtemps après avoir été restreint. Construisez le chemin de réindexation avant de construire le chatbot.

MLSecOps : transformer le Top 10 en pipelines

Le MLSecOps consiste simplement à reconnaître qu'un modèle est un artefact de build, avec une chaîne d'approvisionnement, une porte de déploiement et une posture runtime — comme une image de conteneur, à ceci près qu'il est opaque, stochastique et souvent téléchargé depuis Internet. Quatre plans à couvrir.

Chaîne d'approvisionnement. Les poids sont exécutables : un checkpoint PyTorch historique est un pickle, et le charger exécute du code arbitraire. Imposez safetensors, scannez les artefacts avec un model scanner, épinglez les révisions Hugging Face par SHA de commit et non par branche, et miroitez tout dans un registre interne — des artefacts OCI dans Harbor ou Artifactory fonctionnent très bien et vous font hériter de l'outillage de provenance déjà en place pour les images. Signez avec Sigstore/cosign et publiez un ML-BOM (CycloneDX gère les composants de machine learning) à côté du SBOM.

Build et CI. Les évaluations de sécurité appartiennent au pipeline, pas au pentest annuel. Des outils de red teaming automatisé comme garak, PyRIT (Microsoft) ou promptfoo s'exécutent en job et font échouer le build lorsque la résistance au jailbreak ou à la fuite régresse par rapport à une baseline. Les résultats sont des artefacts de test comme les autres.

name: llm-security-gate
on: [pull_request]
jobs:
  redteam:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install garak promptfoo
      # Sondes injection, fuite de contexte, encodages exotiques
      - run: garak --model_type rest --generator_option_file gen.json \
               --probes promptinject,leakreplay,encoding \
               --report_prefix artifacts/garak
      # Evals sur golden set avec seuil bloquant
      - run: promptfoo eval -c promptfooconfig.yaml --fail-on-error
      - uses: actions/upload-artifact@v4
        with: { name: llm-security, path: artifacts/ }

Déploiement. L'admission control que vous appliquez aux images vaut pour les modèles. Une policy Kyverno ou OPA peut refuser toute charge d'inférence dont l'artefact n'est pas signé, provient d'un registre externe ou ne porte pas de label de niveau de risque. Associez-y une NetworkPolicy en default-deny et une allowlist d'egress : à elle seule, cette mesure transforme la plupart des injections réussies en échec journalisé plutôt qu'en fuite de données.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-models
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-model-artifact
      match:
        any:
          - resources:
              kinds: [Pod]
              namespaces: [ml-serving]
      verifyImages:
        - imageReferences:
            - "registry.interne/models/*"
          attestors:
            - entries:
                - keyless:
                    issuer: https://token.actions.githubusercontent.com
                    subject: https://github.com/acme/model-registry/*

Runtime. Empilez les garde-fous des deux côtés du modèle : classifieur en entrée, classifieur en sortie (Llama Guard, NeMo Guardrails ou équivalent managé), rédaction des données personnelles avant que la requête ne quitte votre périmètre, détection de secrets dans les deux sens. Instrumentez avec les conventions sémantiques GenAI d'OpenTelemetry pour que prompts, appels d'outils, comptes de tokens et latences atterrissent dans les mêmes traces que le reste de la plateforme. Puis câblez des budgets de tokens par tenant et des limites dures d'étapes par run d'agent : LLM10 est le point de convergence entre sécurité et FinOps, et c'est le contrôle le plus souvent absent.

Gouvernance : l'inventaire d'abord

NIST AI RMF, ISO/IEC 42001 et le règlement européen sur l'IA poussent tous dans la même direction : être capable d'énumérer ses systèmes d'IA, leur finalité, leurs données et leur niveau de risque. En pratique, le blocage vient rarement du référentiel — il vient du shadow AI. On ne certifie pas ce qu'on ne voit pas, et une part notable des usages GenAI en grande entreprise passe encore par des clés d'API non gérées et des comptes personnels. Une politique d'egress plus une passerelle par laquelle tout appel modèle doit transiter valent mieux que trois mois de rédaction de politique.

Une feuille de route 90 jours

Si vous partez de zéro, la séquence qui réduit le plus de risque par sprint est la suivante : (1) constituer l'inventaire des cas d'usage GenAI et router tout le trafic par une passerelle unique ; (2) classer chaque cas d'usage par rayon d'explosion, pas par effet de mode, et ramener les privilèges des agents au minimum ; (3) poser les contrôles de supply chain — safetensors, scan, registre interne, vérification de signature à l'admission ; (4) intégrer red teaming automatisé et evals sur golden set dans la CI avec une porte bloquante ; (5) boucler avec le tracing, les budgets de tokens et un playbook d'incident qui traite une injection réussie comme n'importe quelle compromission de credentials.

Rien de tout cela n'exige d'outillage exotique. La vérité inconfortable du MLSecOps, c'est qu'il s'agit à 80 % du DevSecOps que vous maîtrisez déjà, appliqué à une classe d'artefacts que votre équipe plateforme n'a pas encore onboardée — plus une primitive réellement nouvelle : ne jamais accorder à la sortie d'un modèle une autorité qu'elle ne devrait pas avoir.

← Retour au blog