Lors d’un récent test d’intrusion applicatif sur une plateforme de productivité basée sur l’IA, notre équipe s’est échappée du sandbox d’exécution de code de l’un de ses agents et a lu plus de quarante fichiers source du backend — dont l’identifiant de compte AWS, l’e-mail du super-administrateur et la cartographie complète des secrets de la plateforme. Sans exploit, sans privilège particulier : un compte gratuit et deux lignes de SQL ont suffi. Ce cas d’usage anonymisé explique comment, et les quelques lignes de configuration qui referment la faille.
- Un sandbox Python fondé sur RestrictedPython bloquait les imports dangereux, mais n’avait aucune visibilité sur une bibliothèque native C++ (DuckDB) préchargée avant l’application des restrictions.
- Les fonctions de système de fichiers de DuckDB (glob(), read_text()) lisaient directement le code de l’application, car le sandbox s’exécutait dans le répertoire du code de production.
- L’exfiltration a utilisé l’outil « enregistrer un fichier » de l’agent lui-même — aucun serveur externe, aucune connexion sortante.
- Trois réglages corrigent le problème : enable_external_access=false, lock_configuration=true et un répertoire de travail isolé.
Le contexte : un agent IA doté d’un outil de code
La plateforme permet aux utilisateurs de dialoguer avec des agents IA spécialisés. L’un d’eux, un agent d’analyse, exposait un outil execute_code qui exécutait du Python fourni par l’utilisateur à des fins d’analyse de données, dans un sandbox construit sur RestrictedPython. L’outil annonçait même son environnement : « bibliothèques disponibles : pandas, numpy, matplotlib, duckdb ». DuckDB était préchargé et prêt à l’emploi — et c’est précisément là que tout a commencé.
Pourquoi RestrictedPython n’a pas suffi
RestrictedPython réécrit le code de l’utilisateur via un transformateur d’AST sur mesure, puis l’exécute avec un jeu de variables globales restreint. Importer os, subprocess ou socket, ou appeler directement open(), est bloqué. C’est un contrôle de la couche Python : il n’intercepte que ce qui passe par le mécanisme d’import de Python et par l’arbre syntaxique.
Ce qu’il ne voit pas, c’est une extension native C++ déjà instanciée avant l’application des restrictions. DuckDB n’est pas du Python pur : ses fonctions de système de fichiers — read_text(), glob(), read_csv_auto() — sont écrites en C++ et exposées via un objet déjà présent dans le périmètre d’exécution. Il ne reste aucune instruction import à intercepter. Nous n’avons jamais eu besoin d’os ; seulement de duckdb, que la plateforme nous a fourni.
La chaîne d’attaque
Deux requêtes SQL, exécutées via l’agent, ont constitué toute la chaîne. D’abord, énumérer le système de fichiers :
import duckdb
for row in duckdb.sql("SELECT file FROM glob('./**')").fetchall():
print(row[0])
Le répertoire de travail du sandbox se révélait être la racine de l’application. Le listing renvoyait le backend en production : config.py, entitlements.py, les modules d’authentification et toute une arborescence deploy/terraform/. Ensuite, lire n’importe quel fichier :
import duckdb
print(duckdb.sql("SELECT content FROM read_text('./config.py')").fetchone()[0])
Le seul fichier config.py exposait l’identifiant de compte AWS, l’e-mail du super-administrateur et le nom de chaque secret chargé par l’application — une cartographie complète du coffre à secrets. deploy/terraform/secrets.tf confirmait ensuite qu’un unique secret AWS Secrets Manager contenait toutes les clés à la fois : quiconque peut lire ce seul secret contrôle l’ensemble de la plateforme.
Aucun serveur de rappel n’a été nécessaire pour sortir les données. Le même agent disposait d’un outil « enregistrer un fichier » ; nous lui avons demandé de stocker ce que nous avions lu, et la plateforme l’a conservé sur son propre stockage objet, que nous avons récupéré via son API d’artefacts.
Ce qui a été exposé
| Fichier | Ce qu’il révélait |
|---|---|
| config.py | Identifiant de compte AWS, e-mail super-admin, noms des 30+ secrets |
| deploy/terraform/secrets.tf | Un unique secret Secrets Manager contenant toutes les identifiants |
| deploy/terraform/waf.tf | Règles WAF exactes — quels schémas de requêtes étaient bloqués, et lesquels ne l’étaient pas |
| deploy/terraform/iam.tf | Rôles et politiques IAM des comptes de service applicatifs |
| entitlements.py | Toute la logique de droits d’accès et de niveaux d’abonnement |
| + 35 autres fichiers | Flux d’authentification, routes d’organisation, scripts de déploiement et de migration |
Trois failles qui s’additionnent
Aucune erreur unique n’est en cause. Trois décisions indépendantes se sont combinées :
- Une bibliothèque native chargée hors du périmètre restreint. RestrictedPython ne gouverne que la couche Python ; une extension C++ déjà en mémoire lui est invisible. Le même risque vaut pour toute bibliothèque dotée d’E/S natives — lxml, h5py, pyarrow et d’autres.
- Le sandbox s’exécutait dans le répertoire du code source. ./config.py n’était pas un chemin temporaire : c’était le fichier de production. Restreindre le système d’import tout en pointant le répertoire de travail vers son propre code reste un contrôle partiel.
- L’accès au système de fichiers de DuckDB était laissé actif. DuckDB fournit un réglage prévu exactement pour cela — il n’était simplement pas positionné.
Recommandations
Pour toute plateforme qui laisse des utilisateurs non fiables ou des agents IA exécuter du code, la leçon est claire : il faut cloisonner la chaîne de données, pas seulement le langage.
- ImmédiatDésactiver l’accès externe de DuckDB — et le verrouiller
Créez une connexion avec SET enable_external_access=false, puis SET lock_configuration=true afin qu’une requête SQL non fiable ne puisse pas le réactiver. Sans ce verrou, SET enable_external_access=true rouvre le système de fichiers en une seule instruction. Passez les données utilisateur sous forme de DataFrames en mémoire, jamais par des chemins de fichiers.
- ImmédiatIsoler le répertoire de travail du sandbox
Exécutez le code dans un conteneur dédié dont le répertoire de travail est un volume vide. N’y montez jamais le code source, les fichiers de configuration ni les secrets de l’application. Le répertoire courant d’un environnement non fiable ne doit rien contenir de sensible.
- Court termeAuditer chaque bibliothèque préchargée
Pour chaque bibliothèque disponible dans le sandbox, demandez-vous si elle possède des fonctions natives de fichiers ou de réseau. Si oui, vérifiez l’existence d’un réglage d’accès intégré (DuckDB en a un ; beaucoup n’en ont pas) et désactivez-le ; sinon, appuyez-vous sur les contrôles OS ci-dessous.
- Court termeAjouter un confinement au niveau du système
Lorsqu’une bibliothèque n’offre aucun verrou, imposez l’isolation au niveau OS : système de fichiers racine en lecture seule, profils seccomp, un « jailer » tel que nsjail et aucune sortie réseau. Cela rattrape la prochaine bibliothèque présentant la même faiblesse.
- Moyen termeTraiter les outils de l’agent comme une surface d’attaque
Un agent capable d’enregistrer des fichiers ou d’appeler des API est à lui seul un canal d’exfiltration. Modélisez la menace de l’exécution de code avec l’ensemble des outils exposés par l’agent, et non isolément.
Les fonctions d’IA qui exécutent du code utilisateur déplacent la frontière de sécurité du réseau vers l’application elle-même. Une liste noire d’imports Python rassure, mais ne protège qu’une seule couche — le répertoire de travail, les bibliothèques préchargées et les outils de l’agent font tous partie de la véritable surface d’attaque.
Nos équipes Audit & Offensive Security testent précisément ces chemins — sandboxes de code, outils d’agents, configuration cloud — et notre offre Adoption sécurisée de l’IA vous aide à déployer des agents IA sans ouvrir de porte vers votre propre infrastructure.
- Securing DuckDBDocumentation DuckDB — manuel d’exploitation
- Configuration — enable_external_access, lock_configuration, allowed_pathsDocumentation DuckDB
- RestrictedPython — documentationZope Foundation
