Fonctionnement de l'isolation réseau
Découvrez le modèle de sécurité du triage IA tenant compte du code, la frontière réseau externe à l'agent et l'isolation d'un runner GitLab qui empêche un rapport malveillant d'exfiltrer votre code.
Cette traduction est peut-être obsolète. La version anglaise a été mise à jour depuis la dernière traduction de cette page. Voir en anglais →
Pourquoi c’est important
Le triage IA tenant compte du code fournit simultanément à un agent votre code source et le contenu d’un rapport contrôlé par un attaquant. C’est une cible d’injection de prompt. Un chercheur malveillant peut rédiger un rapport qui tente de détourner l’agent, de lui faire lire des secrets puis de les envoyer vers un hôte externe.
La défense ne consiste pas à « demander gentiment à l’agent de s’abstenir ». Même entièrement détourné, l’agent ne doit avoir nulle part où envoyer des données. La frontière réseau s’applique hors de l’agent, au niveau de son environnement. Cette page explique cette frontière et sa configuration.
Danger
Considérez tout contenu de rapport comme hostile. Le prompt de triage entoure les champs de délimiteurs et demande au modèle de ne jamais exécuter leurs instructions. Cette hygiène renforce la défense, mais ne constitue pas la frontière. Celle-ci repose sur le refus par défaut de toute sortie décrit ci-dessous.
Principe fondamental : confinement au niveau de l’environnement
La décision la plus importante est la suivante : la liste d’autorisation réseau propre à l’agent ne sert jamais de frontière de sécurité. Les recommandations d’Anthropic sur le confinement des systèmes agentiques demandent d’abord un confinement au niveau de l’environnement. Les restrictions que l’agent s’impose ne sont qu’une défense supplémentaire.
Une liste propre à l’agent ne suffit pas :
- Les listes d’autorisation se contournent. Claude Code a déjà corrigé de véritables failles de contournement, par exemple une classe de service MCP mal interprétée et une différence de traitement entre SOCKS5 et les proxy HTTP.
- L’agent peut invoquer autre chose. Une injection peut le pousser à utiliser un autre outil, générer une commande ou exploiter une dépendance autorisée.
- Les secrets restent lisibles. Masquer une variable CI dans les journaux ne l’empêche pas d’être lue au moment de l’exécution.
- Une autorisation DNS ne suffit pas. Le rebind DNS, les redirections et les adresses IP directes contournent les contrôles naïfs fondés sur le nom d’hôte.
La frontière doit donc rester en dehors du processus du modèle, dans la configuration réseau et celle du runner.
La frontière : sorties refusées par défaut et proxy vers deux destinations
L’isolation repose sur quatre contrôles appliqués par l’environnement. Le dépôt d’exemple les illustre dans .gitlab-ci.yml et le répertoire infra/.
1. Un runner GitLab autogéré
Vous devez utiliser un runner autogéré. Les runners partagés de GitLab.com ne permettent pas d’imposer vous-même l’isolation du réseau hôte. Le README du dépôt d’exemple en fait un prérequis.
2. Un exécuteur Docker non privilégié
| Réglage | Valeur | Pourquoi |
|---|---|---|
privileged |
false |
Le mode privilégié équivaut presque à l’accès root à l’hôte. Il est exclu pour un chemin alimenté par un rapport non fiable. |
| Exécution en tant que | utilisateur non-root | Supprimez SETUID/SETGID et toute capacité inutile. |
| Socket Docker | non monté | Un socket monté permet de prendre le contrôle de l’hôte. |
| Volumes hôte | aucun | Aucun montage de l’hôte dans la tâche. |
FF_NETWORK_PER_BUILD |
1 |
GitLab crée un réseau bridge dédié à chaque tâche, puis le détruit. L’isolation est reproductible. |
# config.toml on your self-managed runner
[[runners]]
executor = "docker"
environment = ["FF_NETWORK_PER_BUILD=1"]
[runners.docker]
privileged = false
3. Refus de toute sortie au niveau de l’hôte
Warning
GitLab Runner ne fournit aucune liste d’autorisation sortante native. Aucun réglage intégré ne limite les tâches à certains hôtes. Une proposition existe, mais n’est pas disponible. La frontière doit être appliquée au niveau de l’hôte ou de l’espace de noms.
Appliquez une politique de sortie avec DROP par défaut sur le réseau de la tâche à l’aide de iptables/nftables, ou placez la tâche dans un espace de noms réseau sans route vers l’intranet. Toute sortie est refusée par défaut. Ouvrez ensuite exactement les deux destinations ci-dessous, rien d’autre, y compris votre réseau interne.
4. Un proxy sortant qui termine TLS et valide le jeton
La seule route autorisée passe par un proxy qui accepte exactement deux destinations :
- Le point de terminaison d’inférence du LLM, par exemple
https://api.deepseek.com/anthropic. - L’hôte MCP limité de Kit, pour lire le rapport puis renvoyer le triage.
Le proxy ne se contente pas d’épingler ces hôtes :
- il limite les chemins à l’inférence et à MCP. Les sous-API de stockage de fichiers du fournisseur restent bloquées ;
- il valide le jeton de session propre à l’exécution. Un attaquant qui introduit sa propre clé d’API ne peut pas l’utiliser, car le proxy n’accepte que le jeton émis pour cette exécution.
Cette approche reprend le principe du proxy MITM dans la VM qu’Anthropic utilise pour confiner ses propres agents. Un filtre sortant de type harden-runner constitue un analogue conceptuel raisonnable pour l’implémentation de référence.
Le masquage relève de l’hygiène, pas de la protection
Transmettez le jeton MCP limité et la clé du modèle comme variables CI masquées et protégées. Cela évite leur apparition dans les journaux. Mais retenez la limite :
Important
Le masquage remplace seulement la valeur par [MASKED] dans les journaux. Une tâche compromise peut encore la lire pendant l’exécution. La documentation de GitLab précise elle-même que le masquage n’est pas infaillible. Le refus de toute sortie empêche le jeton de quitter la tâche, pas le masque.
Le caractère limité du jeton reste donc essentiel : même lu par une tâche détournée, il ne couvre qu’un rapport pendant une heure, n’autorise qu’une seule écriture et ne peut être envoyé nulle part.
Prouvez-le avec le canari d’injection
Le dépôt d’exemple fournit un rapport de test dans examples/sample-report. Il contient un canari d’injection qui tente d’appeler un hôte externe avec curl. Exécutez le pipeline et vérifiez que la frontière sortante bloque l’appel. Si la requête réussit, votre environnement n’est pas isolé. Corrigez le pare-feu avant de connecter Kit.
Vérifiez également qu’après la tâche :
- aucune route vers le réseau interne n’était disponible ;
- seul le proxy pouvait être joint ;
- le journal du proxy ne contient que l’inférence et les appels MCP attendus ;
- le réseau propre à la tâche a bien été détruit.
Le fichier SECURITY.md du dépôt d’exemple le rappelle.
Checklist
- Utilisez un runner GitLab autogéré, pas les runners SaaS partagés
-
Configurez l’exécuteur Docker avec
privileged = false, un utilisateur non-root, aucun socket Docker et aucun volume hôte -
Activez
FF_NETWORK_PER_BUILD=1pour isoler chaque tâche - Refusez toute sortie par défaut au niveau de l’hôte avec iptables/nftables ou un espace de noms réseau
- Autorisez dans le proxy exactement deux hôtes et chemins : le modèle et l’hôte MCP de Kit
- Validez le jeton propre à l’exécution et bloquez les sous-API de stockage du fournisseur
- Traitez le jeton comme limité à un rapport, une heure et une écriture ; le masquage n’est qu’une mesure d’hygiène
- Exécutez le canari d’injection et confirmez le blocage de l’exfiltration
- Épinglez une version récente de Claude Code ou du bac à sable, en défense supplémentaire