Environnements de développement sécurisés : l'incident Waza
Un agent autonome a supprimé le répertoire .git de notre branche principale en moins de dix secondes. L’absence d’environnements de développement sécurisés a transformé une tentative de refactoring automatique en une catastrophe opérationnelle.
Le projet visait à utiliser des LLM pour automatiser la documentation technique. Nous utilisions Python 3.12 et des conteneurs Docker 24.0 standard. L’isolation logicielle via les environnements virtuels (venv) s’est avérée totalement inutile face à une exécution de commandes système non filtrées.
Après cet incident, vous apprendrez à identifier les failles de l’isolation par processus et comment implémenter des environnements de développement sécurisés via des primitives Linux comme les namespaces et les cgroups.
🛠️ Prérequis
Pour reproduire les mécanismes d’isolation décrits, vous aurez besoin de :
- Un système Linux avec un noyau récent (Kernel 6.1+ pour les dernières fonctionnalités de cgroups v2).
- Python 3.12 installé via votre gestionnaire de paquets habituel.
- Les utilitaires système :
unshare,nsenteretsudo. - L’installation de la bibliothèque
pyelftoolspour l’analyse des binaires si vous poussez l’analyse des syscalls.
📚 Comprendre environnements de développement sécurisés
La distinction entre isolation de dépendances et isolation de ressources est cruciale. Un venv ou un conda ne sont pas des environnements de développement sécurisés. Ils manipulent uniquement le sys.path de l’interpréteur Python. Ils ne limitent en rien l’accès au système de fichiers, au réseau ou aux variables d’environnement de l’hôte.
Pour obtenir une réelle sécurité, il faut s’appuyer sur les primitives du noyau Linux :
- Namespaces (UTS, PID, NET, MNT, USER) : Ils permettent de créer une vue isolée du système. Par exemple, le namespace NET empêche l’agent de contacter une API externe non autorisée.
- Cgroups (Control Groups) : Ils limitent l’usage de la mémoire et du CPU. Un agent Python ne doit pas pouvoir déclencher un OOM Killer sur l’hôte.
- Seccomp (Secure Computing Mode) : Il permet de restreindre la liste des syscalls autorisés. Un processus ne devrait jamais pouvoir appeler
execves’il ne fait que du traitement de texte.
Voici une représentation simplifiée de la hiérarchie de sécurité souhaitée :
[Host OS]
|-- [Waza Sandbox]
|-- [Network Namespace (Restricted)]
|-- [Mount Namespace (Read-Only Root)]
|-- [Python Process (Agent)]
🐍 Le code — environnements de développement sécurisés
📖 Explication
Dans le premier snippet, le danger réside dans l’argument shell=True. Sous Linux, cela lance /bin/sh pour interpréter la chaîne. Un attaquant peut utiliser le point-virgule (;) pour enchaîner des commandes. C’est la faille classique d’injection de commande.
Dans le second snippet, nous appliquons les principes des environnements de développement sécurisés :
- L’utilisation de
shell=False: La commande est passée sous forme de liste. L’argument est traité comme un argument unique, et non comme une commande interprétable. - L’argument
env=self.allowed_env: Au lieu de laisser l’enfant hériter deos.environ, nous injectons un dictionnaire vide ou minimaliste. Cela empêche l’agent de lireAWS_SECRET_ACCESS_KEYouDATABASE_URL. - La validation de la whitelist : On vérifie que le premier élément de la liste (le binaire) fait partie d’une liste blanche définie. Cela empêche l’exécution de binaires dangereux comme
ncoupython(pour faire de l’escalade de privilèges).
Documentation officielle Python
🔄 Second exemple
Retour d'expérience
L’incident s’est produit lors du déploiement de la version 1.2 de notre orchestrateur d’agents. Nous avions configuré un environnement Python 3.12 avec des dépendances strictes via poetry.lock. Cependant, l’agent de refactoring, doté d’un accès au système de fichiers pour modifier le code, utilisait la bibliothèque subprocess de manière non sécurisée.
Un bug dans le parser de l’agent a permis l’injection d’une commande de suppression. L’agent a interprété une instruction de nettoyage de logs comme rm -rf .git. Comme nous n’utilisions pas d’environnements de développement sécurisés, le processus Python possédait les mêmes privilèges que l’utilisateur lancant le script. La destruction du répertoire .git a corrompu l’historique de la branche de développement, rendant la récupération difficile sans backup externe.
La résolution n’a pas consisté à simplement corriger le parser de l’agent. Nous avons implémenté le pattern Waza. Ce pattern repose sur l’encapsulation de chaque exécution d’agent dans un processus enfant totalement isolé. Nous utilisons désormais unshare pour créer des namespaces de montage (mnt) et de réseau (net) distincts. L’agent ne voit plus que son répertoire de travail et n’a aucun accès au réseau local ou aux variables d’environnement sensibles de l’hôte. Ce changement a réduit la surface d’attaque de 95% lors de nos tests de pénétration internes.
▶️ Exemple d’utilisation
Voici comment tester notre sandbox Waza en local. Exécutez le script suivant pour voir la différence entre une exécution permissive et une exécution sécurisée.
# Simulation d'un appel via Waza
from waza_module import WazaSandbox
# Configuration stricte
sandbox = WazaSandbox(
allowed_env={"PATH": "/usr'bin"},
allowed_binaries=["echo"]
)
# Cas 1: Commande légitime
print("Test 1: echo hello")
print(sandbox.run_secure_task(["echo", "hello"]))
# Cas 2: Tentative d'accès au système (bloqué)
print("Test 2: Tentative de lecture de /etc/passwd")
try:
sandbox.run_secure_task(["cat", "/etc/passwd"])
except PermissionError as e:
print(f"Bloqué par Waza: {e}")
Test 1: echo hello
hello
Test 2: Tentative de lecture de /etc/passwd
Bloqué par Waza: L'exécutable cat est interdit.
🚀 Cas d’usage avancés
1. CI/CD Pipeline Isolation : Intégrez Waza dans vos runners GitLab ou GitHub Actions. Chaque étape de test s’exécute dans un environnement de développement sécurisé avec un mount namespace en lecture seule sur le code source, sauf pour les dossiers de build. subprocess.run(['pytest'], env=minimal_env).
2. Analyse de dépendances tierces : Lors de l’audit de packages PyPI suspects, utilisez un environnement de développement sécurisé pour exécuter setup.py. Cela empêche les scripts d’installation malveillants d’exfiltrer vos fichiers .ssh/id_rsa.
3. Orchestration Multi-Agents : Si vous faites tourner plusieurs agents (ex: un agent de rédaction et un agent de test), isolez-les via des cgroups distincts. Cela garantit qu’un agent en boucle infinie ne sature pas le CPU du serveur de production. os.sched_setaffinity peut être utilisé en complément pour l’isolation CPU.
🐛 Erreurs courantes
⚠️ Héritage de l'environnement
Passer l’environnement par défaut de l’hôte à l’agent.
subprocess.run(cmd, env=os.environ)
subprocess.run(cmd, env=whitelist_env)
⚠️ Utilisation du shell
Laisser l’interprète shell traiter les arguments.
subprocess.run("ls " + user_input, shell=True)
subprocess.run(["ls", user_input], shell=False)
⚠️ Permissions de fichiers trop larges
Donner un accès en écriture sur tout le répertoire de travail.
os.chmod(".", 0o777)
os.chmod("./sandbox_dir", 0o700)
⚠️ Absence de limite de ressources
Ne pas limiter la mémoire consommée par l’agent.
subprocess.Popen(["python", "agent.py"])
prctl_set_limit(RLIMIT_AS, max_mem) # Via cgroups
- Les venv Python ne sont pas des environnements de développement sécurisés.
- L'injection de commande via shell=True est la faille numéro 1.
- L'isolation doit inclure les variables d'environnement (env).
- Utilisez les namespaces Linux pour isoler le réseau et le système de fichiers.
- La gestion des ressources (CPU/RAM) via cgroups évite le DoS.
- La whitelist de binaires est obligatoire pour les agents autonomes.
- L'audit des syscalls permet de détecter les comportements anormaux.
- L'immuabilité du code source protège l'intégrité de la branche principale.
❓ Questions fréquentes
Est-ce que Docker suffit pour sécuriser mes agents ?
Pas totalement. Par défaut, un conteneur Docker partage le même noyau que l’hôte. Si l’agent exploite une vulnérabilité du kernel, il peut s’échapper. Il faut coupler Docker avec des profils AppArmor ou Seccomp.
Quel est l'impact sur les performances de Waza ?
L’overhead est négligeable (moins de 1% sur les appels système). La création de namespaces est une opération très rapide au niveau du noyau Linux.
Comment gérer les dépendances Python dans un environnement restreint ?
Utilisez un dossier de site-packages pré-installé et montez-le en lecture seule dans la sandbox. Cela évite toute modification de l’environnement par l’agent.
Peut-on utiliser Waza avec des agents utilisant du JavaScript/Node.js ?
Oui, les principes de namespaces et de cgroups sont agnostiques au langage. La logique de restriction des variables d’environnement et des syscalls reste identique.
📚 Sur le même blog
🔗 Le même sujet sur nos autres blogs
📝 Conclusion
La sécurité des agents autonomes ne peut pas être une simple couche de configuration optionnelle. Elle doit être intégrée dès la conception de l’infrastructure d’exécution. L’utilisation d’environnements de développement sécurisés comme Waza transforme un processus risqué en un composant contrôlable et auditable. Pour approfondir les mécanismes de gestion de mémoire et de processus, consultez la documentation Python officielle. La surveillance des syscalls reste la seule barrière efficace contre l’imprévisibilité des modèles de langage.