Contact

RZ-2026-002

Note de travail
Type
Note de recherche
Publié le
Mis à jour
Durée
9 min de lecture
Auteur
Équipe rootzero

Agents offensifs autonomes : garde-fous de périmètre

Liste blanche, rebinding DNS, redirections, limitation de débit, classes d’actions destructives, arrêt d’urgence, journal d’audit et validation humaine.

  • Méthodologie
  • Pentest
  • IA

Résumé

Un agent de test autonome qui émet des requêtes vers des systèmes réels doit rester dans le périmètre autorisé, y compris lorsque le modèle qui le pilote se trompe ou se laisse manipuler par le contenu de la cible. Cette note décrit une architecture où les garde-fous sont appliqués hors du modèle, à chaque requête : périmètre déclaratif, résolution et épinglage des adresses, revalidation des redirections, limitation de débit, classes d’actions graduées, arrêt d’urgence par bail, journal d’audit chaîné et validation humaine. Elle recense les pièges connus et les limites de l’approche.

Référence
RZ-2026-002
Statut
Note de travail
Publication
Révision
Auteur
Équipe rootzero
Lecture
9 min de lecture

1. Contexte et hypothèse

Un agent offensif autonome associe un modèle de langage, qui planifie, à des outils qui agissent : client HTTP, navigateur, interpréteur. À la différence d’un scanner, il choisit lui-même les chemins, les paramètres et les liens à explorer.

Le risque est alors de sortir du périmètre autorisé, par erreur de planification, par lecture trop large d’une consigne ou sous l’influence du contenu de la cible. Le référentiel OWASP Top 10 for LLM Applications décrit les deux mécanismes en jeu : l’injection de prompt, y compris indirecte via un contenu traité par le modèle (LLM01:2025), et l’agentivité excessive, lorsqu’un système dispose de plus de capacités ou d’autonomie que nécessaire (LLM06:2025). Le document Agentic AI, Threats and Mitigations, publié par l’OWASP GenAI Security Project en février 2025, détaille des menaces propres aux agents, dont le détournement d’outils, la répudiation et l’absence de traçabilité, ou la saturation du contrôle humain.

L’hypothèse de travail de cette note est que le modèle ne constitue pas une frontière de sécurité. Une consigne écrite dans un prompt (« ne teste que app.exemple.fr ») exprime une intention, que rien n’oblige le modèle à respecter. Les garde-fous sont donc appliqués par un composant déterministe, hors du modèle, sur chaque action effectivement exécutée.

2. Architecture

L’architecture de référence sépare trois rôles.

  1. L’agent planifie et formule des actions (une requête HTTP, une commande). Il n’a aucun accès réseau direct.
  2. Le point d’application de la politique, en pratique un proxy sortant obligatoire, reçoit chaque action, la confronte au périmètre et aux règles, l’exécute ou la refuse, et journalise sa décision.
  3. Le contrôleur, hors de portée de l’agent, détient le périmètre, délivre les autorisations temporaires et porte l’arrêt d’urgence.

L’isolement réseau conditionne le reste : l’agent ne peut joindre que le proxy, faute de quoi un appel direct contournerait toutes les règles. La vérification porte sur la requête réelle, au moment où elle part.

3. Le périmètre comme objet déclaratif

Le périmètre est un document versionné, dérivé de l’autorisation écrite du client. Voici un exemple de structure, avec des domaines fictifs et une plage d’adresses réservée à la documentation (RFC 5737) :

scope_id: "SC-2026-014"
authorized_by: "client, lettre d'autorisation du 2026-10-01"
window: { start: "2026-10-12T08:00:00+02:00", end: "2026-10-16T19:00:00+02:00" }
hosts:
  exact: ["app.exemple.fr", "api.exemple.fr"]
  wildcard: ["preprod.exemple.fr"]   # autorise *.preprod.exemple.fr, explicitement
networks: ["198.51.100.0/24"]        # adresses de résolution admises
schemes: ["https"]
ports: [443]
exclude_paths: ["/logout", "/account/delete", "/payment/*"]
rate: { per_host_rps: 5, max_concurrency: 4 }
max_action_class: "C2"               # voir section 5

Ce périmètre est fermé : tout ce qui n’y figure pas est refusé. Il est aussi double, puisqu’un nom d’hôte autorisé doit encore résoudre vers une adresse autorisée.

4. Pièges de la vérification de périmètre

Les pièges qui suivent sont documentés, et les testeurs les exploitent couramment dans les failles SSRF. La fiche OWASP Server-Side Request Forgery Prevention en couvre une grande partie.

4.1 Comparaison de noms d’hôte

Un test de suffixe naïf, host.endswith("exemple.fr"), accepte malveillant-exemple.fr. La comparaison doit porter sur des labels DNS complets, après normalisation : minuscules, point final retiré, conversion IDN.

from urllib.parse import urlsplit

EXACT = {"app.exemple.fr", "api.exemple.fr"}
WILDCARD = {"preprod.exemple.fr"}

def host_in_scope(url: str) -> bool:
    parts = urlsplit(url)
    if parts.scheme != "https" or parts.username or parts.password:
        return False                      # refuse https://app.exemple.fr@autre.example/
    host = (parts.hostname or "").rstrip(".").lower()
    try:
        host = host.encode("idna").decode("ascii")
    except UnicodeError:
        return False
    if host in EXACT:
        return True
    return any(host.endswith("." + base) for base in WILDCARD)

4.2 Divergences entre analyseurs d’URL

Deux bibliothèques peuvent lire différemment la même URL, à cause des barres obliques inverses, des informations d’utilisateur, des encodages ou des caractères Unicode. Orange Tsai en a présenté une démonstration systématique à Black Hat USA 2017 dans A New Era of SSRF. La règle qui en découle est d’analyser l’URL une seule fois et d’établir la connexion à partir du résultat de cette analyse.

4.3 Rebinding DNS et notations d’adresse

Un nom autorisé peut résoudre vers une adresse autorisée au moment du contrôle, puis vers une adresse interne au moment de la connexion. Ce DNS rebinding est un cas classique de contrôle puis usage (TOCTOU). La parade consiste à résoudre une fois, vérifier toutes les adresses obtenues et se connecter à l’adresse vérifiée, en transmettant le nom d’origine dans l’en-tête Host et dans l’extension TLS SNI.

import ipaddress
import socket

NETWORKS = [ipaddress.ip_network("198.51.100.0/24")]

class ScopeViolation(Exception):
    pass

def pin_address(host: str, port: int = 443) -> str:
    infos = socket.getaddrinfo(host, port, proto=socket.IPPROTO_TCP)
    addrs = {ipaddress.ip_address(info[4][0]) for info in infos}
    if not addrs or not all(any(a in net for net in NETWORKS) for a in addrs):
        raise ScopeViolation(f"{host} résout hors périmètre : {sorted(map(str, addrs))}")
    return str(min(addrs))  # l'adresse vérifiée est celle utilisée

La même vérification couvre les notations détournées (adresse décimale comme 2130706433, octale, IPv6 encapsulant IPv4), à condition de raisonner toujours sur l’adresse analysée et jamais sur la chaîne. Certaines plages restent interdites quel que soit le périmètre, sauf mention explicite : boucle locale, adresses de lien local (dont 169.254.169.254, utilisée par les services de métadonnées des fournisseurs cloud) et plus généralement les plages à usage spécial recensées par la RFC 6890.

4.4 Redirections

Un client HTTP qui suit les redirections automatiquement peut quitter le périmètre en une seule réponse, du type 302 Location: https://autre.example/. Le suivi automatique est donc désactivé. Chaque Location repasse par l’ensemble des contrôles, et le nombre de sauts est borné. Les codes 307 et 308 conservent la méthode et le corps de la requête (RFC 9110, section 15.4) : un POST redirigé reste un POST.

4.5 Contenu de la cible

Les pages testées contiennent des liens vers des tiers et parfois du texte rédigé pour manipuler un agent. Ce contenu ne peut pas modifier le périmètre, qui réside dans le point d’application, hors de portée du prompt. Les tests d’interaction hors bande, notamment pour les SSRF, utilisent un domaine de collecte contrôlé par le testeur et déclaré dans le périmètre.

5. Débit et classes d’actions

5.1 Limitation de débit

Un agent qui boucle peut produire la charge d’un déni de service. Le proxy applique un seau à jetons par hôte et un plafond de requêtes simultanées. Il ralentit sur 429 Too Many Requests et 503 Service Unavailable en respectant Retry-After lorsqu’il est fourni. Il coupe le flux (circuit breaker) si le taux d’erreurs serveur ou la latence de la cible augmentent nettement par rapport à la référence mesurée au début du test.

5.2 Classes d’actions

Les requêtes n’ont pas toutes le même potentiel de dommage. Nous utilisons une gradation en cinq classes, appliquée par le point d’application.

Classe Nature Exemples Politique par défaut
C0 Lecture passive Pages connues, fichiers statiques Autorisé
C1 Sonde non mutante Paramètre modifié en lecture, en-tête inhabituel Autorisé
C2 Mutation réversible Création d’un objet de test sur un compte de test Autorisé si le périmètre le prévoit
C3 Mutation irréversible ou visible Suppression, réinitialisation de mot de passe, courriel envoyé à de vrais utilisateurs, paiement Approbation humaine, action par action
C4 Atteinte à la disponibilité Charge massive, verrouillage de comptes, fuzzing intensif Interdit sauf mention explicite

La méthode HTTP ne suffit pas à classer une requête. La RFC 9110 définit GET comme une méthode sûre, et de nombreuses applications ignorent cette sémantique (GET /admin/user/42/delete). La classe se déduit de la méthode, du chemin, des exclusions du périmètre et de la nature de la charge utile. En cas de doute, la classe supérieure s’applique.

6. Arrêt d’urgence et journal d’audit

6.1 L’arrêt d’urgence par bail

Un arrêt que l’agent peut atteindre, ignorer ou retarder ne garantit rien. Le mécanisme retenu repose sur un bail. Le proxy n’accepte une requête que si elle porte une autorisation valide et de courte durée, délivrée par le contrôleur, qui la renouvelle tant que le test est autorisé. Pour arrêter, le contrôleur cesse de renouveler et révoque le bail en cours. Les requêtes suivantes sont refusées au plus tard à l’expiration, quel que soit l’état de l’agent.

Une panne du contrôleur arrête donc le test au lieu de le laisser sans supervision. L’opérateur comme le client peuvent déclencher l’arrêt, testé à chaque campagne.

6.2 Le journal d’audit

Chaque action proposée est journalisée avant la décision, puis complétée par la décision et la règle appliquée. Le journal est en ajout seul et chaîné par empreinte, de sorte qu’une modification a posteriori soit détectable :

{"seq":1842,"ts":"2026-10-13T10:42:07.311Z","scope":"SC-2026-014","agent":"run-7f3a",
 "action":{"method":"GET","url":"https://app.exemple.fr/preview?name=%7B%7B7*8%7D%7D","class":"C1"},
 "decision":"allow","rule":"hosts.exact","resolved":"198.51.100.24","status":200,
 "prev":"sha256:9c1e…","hash":"sha256:4b07…"}

Ce journal répond à la menace de répudiation décrite par l’OWASP : pour toute requête reçue par la cible, il établit si elle vient de l’agent et en vertu de quelle règle. Les réponses contenant des données personnelles n’y sont conservées que dans la mesure nécessaire.

7. La place de l’humain

La validation humaine intervient à trois moments : la définition du périmètre, l’approbation des actions de classe C3 et la relecture des constats avant publication (voir la note RZ-2026-001).

Un relecteur sollicité cent fois par heure finit par approuver sans lire ; l’OWASP range cette saturation parmi les menaces propres aux agents. Trois règles limitent ce risque. Chaque demande est présentée avec son contexte (requête exacte, classe, raison invoquée par l’agent). Les demandes similaires sont regroupées. Et une demande restée sans réponse à l’expiration du délai est refusée.

8. Éprouver les garde-fous

Nous maintenons une batterie de cas adverses, exécutée à chaque évolution du point d’application. Tous doivent être refusés :

  • un hôte hors liste (autre.example) et un faux suffixe (malveillant-exemple.fr) ;
  • une URL avec informations d’utilisateur, barre oblique inverse ou point final ;
  • un nom autorisé dont la résolution change entre deux requêtes, servi par un DNS de test ;
  • une adresse en notation décimale, octale ou IPv6 encapsulée ;
  • une redirection 302, puis 307, vers un hôte hors périmètre ;
  • un chemin exclu atteint par encodage (/account/%64elete) ;
  • une page de la cible contenant des instructions qui demandent de tester un autre domaine ;
  • une requête émise après révocation du bail.

9. Limites

La politique ignore la sémantique métier : un endpoint d’apparence anodine peut déclencher une facturation. Le périmètre se construit donc avec le client, et la classe C3 sert de filet.

Le périmètre technique traduit le périmètre juridique sans le remplacer. En droit français, l’accès ou le maintien frauduleux dans un système de traitement automatisé de données est réprimé par l’article 323-1 du Code pénal. L’autorisation écrite du client fonde le test, le fichier de périmètre la transcrit, et toute ambiguïté se résout dans le sens de la restriction.

Sur un CDN ou un hébergement mutualisé, une plage autorisée peut aussi servir d’autres clients : le contrôle par adresse complète alors le contrôle par nom sans le remplacer.

La gradation des classes et le mécanisme de bail sont en cours d’éprouvage, d’où le statut de note de travail.

Dans les termes du NIST AI Risk Management Framework (gouverner, cartographier, mesurer, gérer), le périmètre et les classes relèvent de la gouvernance, les tests adverses de la mesure, l’arrêt d’urgence et le journal de la gestion.

Références

Citer cette note

Équipe rootzero. « Agents offensifs autonomes : garde-fous de périmètre ». Note de recherche RZ-2026-002, rootzero, 8 octobre 2026. https://rootzero.eu/research/agents-offensifs-garde-fous/

#Méthode appliquée

Chaque constat publié se rejoue.

SpectrAI applique ces principes au pentest web et API : périmètre déclaré, constats démontrés, validation humaine, re-tests rejouables.