Politiques de sécurité pour l’exécution du code
Restrictions de capacités, isolation réseau, limites du système de fichiers et délais d’expiration.
Politiques de sécurité pour l’exécution du code est une leçon AI Agents gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Agents, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Agents comprend 4 leçons au total.
Pourquoi les politiques ne suffisent pas à elles seules
Dire au LLM « n’accédez pas au réseau » n’est pas un contrôle de sécurité — c’est une indication. Le LLM peut l’ignorer, être contourné, ou le code généré peut enfreindre indirectement la règle.
Les véritables politiques de sécurité doivent être appliquées au niveau de la couche d’exécution, et pas uniquement dans l’invite.
Présentation des restrictions de capacités
Les restrictions de capacités limitent ce qu’un processus exécuté dans un bac à sable peut faire au niveau de l’OS. Les capacités Linux divisent les privilèges de superutilisateur en autorisations précises qui peuvent être supprimées individuellement.
Supprimer toutes les capacités sauf le minimum nécessaire s’appelle le principe du moindre privilège.
import docker
client = docker.from_env()
# Drop all Linux capabilities
output = client.containers.run(
'python:3.12-slim',
'python -c "print(\"safe run\")"',
cap_drop=['ALL'],
network_disabled=True,
mem_limit='256m',
auto_remove=True
)
print(output.decode())Bloquer les écritures disque en dehors de /tmp
Utilisez un système de fichiers racine en lecture seule et ne montez que /tmp en écriture. Cela empêche le code de modifier les fichiers système ou de conserver des données en dehors de la zone temporaire désignée.
import tempfile, docker
client = docker.from_env()
with tempfile.TemporaryDirectory() as tmpdir:
output = client.containers.run(
'python:3.12-slim',
'python -c "open(\"/tmp/ok.txt\",\"w\").write(\"ok\"); print(open(\"/tmp/ok.txt\").read())"',
read_only=True,
tmpfs={'/tmp': 'size=64m,mode=1777'},
network_disabled=True,
mem_limit='128m',
auto_remove=True
)
print(output.decode()) # 'ok' — /tmp write allowed
# Writing anywhere else raises ReadOnlyFileSystemProfils seccomp : filtrer les appels système
seccomp (mode de calcul sécurisé) permet de définir une liste blanche d’appels système autorisés. Tout appel système qui ne figure pas dans la liste provoque l’arrêt du processus avec SIGSYS.
Docker fournit un profil seccomp par défaut. Vous pouvez fournir un profil JSON personnalisé pour appliquer des restrictions supplémentaires.
import json, docker
client = docker.from_env()
# Minimal seccomp profile: only allow read, write, open, close, exit
seccomp_profile = json.dumps({
'defaultAction': 'SCMP_ACT_ERRNO',
'architectures': ['SCMP_ARCH_X86_64'],
'syscalls': [
{'names': ['read', 'write', 'open', 'openat', 'close',
'fstat', 'mmap', 'mprotect', 'munmap', 'brk',
'exit', 'exit_group', 'rt_sigaction',
'rt_sigprocmask', 'futex'],
'action': 'SCMP_ACT_ALLOW'}
]
})
output = client.containers.run(
'python:3.12-slim',
'python -c "print(1+1)"',
security_opt=[f'seccomp={seccomp_profile}'],
auto_remove=True
)
print(output.decode())Limites de ressources : CPU, mémoire, temps
Les trois limites de ressources que tout bac à sable devrait appliquer :
- Mémoire :
mem_limit— empêcher les attaques OOM - CPU :
nano_cpusoucpu_quota— empêcher l’épuisement du CPU - Temps : délai d’exécution — empêcher les boucles infinies
output = client.containers.run(
'python:3.12-slim',
'python -c "import time; time.sleep(100)"',
mem_limit='128m',
nano_cpus=500_000_000, # 0.5 CPU
network_disabled=True,
auto_remove=True,
timeout=10 # kill after 10 seconds
)
# Raises ReadTimeout after 10 secondsMettre les importations Python sur liste blanche
Avant d’exécuter le code, analysez l’AST pour repérer les instructions d’importation. Refusez toute importation qui ne figure pas sur la liste blanche. Il s’agit d’une couche de défense en profondeur — pas d’un remplacement des contrôles au niveau de l’OS, mais d’un moyen utile de détecter rapidement les violations évidentes des politiques.
import ast
ALLOWED_IMPORTS = {'math', 'statistics', 'json', 'csv', 'datetime', 'collections', 're'}
def check_imports(code: str) -> list[str]:
tree = ast.parse(code)
blocked = []
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
if alias.name.split('.')[0] not in ALLOWED_IMPORTS:
blocked.append(alias.name)
elif isinstance(node, ast.ImportFrom):
if node.module and node.module.split('.')[0] not in ALLOWED_IMPORTS:
blocked.append(node.module)
return blocked
print(check_imports('import socket; print(1)')) # ['socket']
print(check_imports('import math; print(math.pi)')) # []Détection des schémas de code dangereux
Au-delà des imports, vérifiez les schémas qui contournent les restrictions d'importation : __import__, exec(), eval(), compile(), open() avec des chemins situés en dehors de /tmp.
import ast
DANGEROUS_CALLS = {'exec', 'eval', 'compile', '__import__', 'breakpoint'}
def check_dangerous_calls(code: str) -> list[str]:
tree = ast.parse(code)
found = []
for node in ast.walk(tree):
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
if node.func.id in DANGEROUS_CALLS:
found.append(node.func.id)
elif isinstance(node.func, ast.Attribute):
if node.func.attr in DANGEROUS_CALLS:
found.append(node.func.attr)
return found
print(check_dangerous_calls('exec("import os")')) # ['exec']Limites de taille de la sortie
Une boucle d'affichage sans fin peut générer des gigaoctets de sortie. Limitez stdout/stderr à une taille raisonnable (par exemple, 1 Mo), puis tronquez la sortie ou arrêtez le conteneur si cette limite est dépassée.
MAX_OUTPUT_BYTES = 1 * 1024 * 1024 # 1 MB
def run_with_output_limit(client, code: str) -> dict:
container = client.containers.run(
'python:3.12-slim',
f'python -c "{code}"',
mem_limit='256m',
network_disabled=True,
detach=True
)
try:
container.wait(timeout=30)
logs = container.logs(stdout=True, stderr=True)
if len(logs) > MAX_OUTPUT_BYTES:
return {'success': False, 'error': 'Output too large'}
return {'success': True, 'output': logs.decode(errors='replace')}
finally:
container.remove(force=True)
if __name__ == '__main__':
class FakeContainer:
def wait(self, timeout=30): pass
def logs(self, stdout=True, stderr=True): return b'Analysis complete: 42 rows processed.'
def remove(self, force=True): pass
class FakeContainers:
def run(self, *args, **kwargs): return FakeContainer()
class FakeClient:
containers = FakeContainers()
result = run_with_output_limit(FakeClient(), 'print(42)')
print('Success:', result['success'])
print('Output :', result['output'])
Indicateur no-new-privileges
L'option de sécurité no-new-privileges empêche le processus d'obtenir des privilèges supplémentaires via des binaires setuid ou sudo. Même si le code ouvre un interpréteur de commandes, il ne peut pas élever ses privilèges.
output = client.containers.run(
'python:3.12-slim',
'python -c "import os; print(os.getuid())"',
user='nobody', # run as non-root
security_opt=['no-new-privileges:true'], # no privilege escalation
cap_drop=['ALL'], # no capabilities
network_disabled=True,
mem_limit='128m',
auto_remove=True
)
print(output.decode()) # numeric UID of 'nobody'Combinaison de toutes les politiques
Un bac à sable de niveau production combine toutes les couches : capacités du système d'exploitation supprimées, profil seccomp, système de fichiers en lecture seule, no-new-privileges, réseau désactivé, limites de ressources et délai d'expiration. La défense en profondeur garantit qu'un contournement d'une couche est bloqué par une autre.
import tempfile, json, docker
client = docker.from_env()
def maximum_security_run(code: str) -> dict:
with tempfile.TemporaryDirectory() as tmpdir:
try:
out = client.containers.run(
'python:3.12-slim',
f'python -c "{code}"',
user='nobody',
security_opt=['no-new-privileges:true'],
cap_drop=['ALL'],
network_disabled=True,
read_only=True,
tmpfs={'/tmp': 'size=32m,mode=1777'},
mem_limit='128m',
nano_cpus=500_000_000,
auto_remove=True,
timeout=15
)
return {'success': True, 'output': out.decode()}
except Exception as e:
return {'success': False, 'error': str(e)}Journalisation des violations de politique
Lorsqu'une violation de politique est détectée (importation bloquée, appel dangereux, délai d'expiration), consignez l'événement avec l'ID de session de l'agent, le hachage de l'extrait de code et le type de violation. Ces données alimentent un tableau de bord de sécurité et permettent d'ajuster les politiques au fil du temps.
import hashlib, logging, time
logging.basicConfig(level=logging.INFO)
security_logger = logging.getLogger('sandbox.security')
def policy_check_and_run(code: str, session_id: str) -> dict:
violations = check_imports(code) + check_dangerous_calls(code)
code_hash = hashlib.sha256(code.encode()).hexdigest()[:16]
if violations:
security_logger.warning(
'POLICY_VIOLATION session=%s hash=%s violations=%s',
session_id, code_hash, violations
)
return {'success': False, 'error': f'Blocked: {violations}'}
result = maximum_security_run(code)
security_logger.info(
'EXEC session=%s hash=%s success=%s',
session_id, code_hash, result['success']
)
return resultQue fait le paramètre 'defaultAction: SCMP_ACT_ERRNO' du profil seccomp ?
Les profils seccomp contrôlent les appels système qu'un processus est autorisé à effectuer. Le champ defaultAction détermine ce qui se produit pour les appels système qui ne sont pas répertoriés explicitement dans le profil.
Récapitulatif des politiques de sécurité
La sécurité efficace de l'exécution du code nécessite plusieurs couches : capacités Linux supprimées, filtrage des appels système par seccomp, système de fichiers en lecture seule avec espace de travail tmpfs, no-new-privileges, réseau désactivé, limites de mémoire, de CPU et de temps, ainsi que vérification des imports au niveau de l'AST.
Aucune couche unique ne suffit — la défense en profondeur signifie que chaque couche compense les contournements des autres.
Questions Fréquemment Posées
La leçon « Politiques de sécurité pour l’exécution du code » est-elle gratuite ?
Oui — le texte complet de « Politiques de sécurité pour l’exécution du code » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Agents, passe à CoddyKit PRO. Le cours AI Agents comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Politiques de sécurité pour l’exécution du code » ?
Restrictions de capacités, isolation réseau, limites du système de fichiers et délais d’expiration. Tu pratiques AI Agents avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer AI Agents ?
Aucune expérience préalable n'est requise. AI Agents sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.
Combien de temps prend la leçon « Politiques de sécurité pour l’exécution du code » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon AI Agents ?
Oui. Chaque leçon AI Agents inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Bacs à sable d’agents fondés sur Docker
- Isolation par VM pour les agents exécutant du code hautement sécurisé
- E2B et services de bacs à sable cloud
- Politiques de sécurité pour l’exécution du code