Aller au contenu principal

Sécurité et sandboxing

Mis à jour le 29 juillet 2026

Donner le contrôle à une IA : les risques

Computer Use donne à un modèle d’IA la capacité d’agir sur un ordinateur. C’est puissant mais potentiellement dangereux. Un agent mal configuré pourrait naviguer vers des sites malveillants, exécuter du code arbitraire, ou exposer des données sensibles. Le scénario n’a rien de théorique : il suffit qu’une page visitée contienne des instructions rédigées à l’intention du modèle pour que celui-ci, s’il n’est pas contraint par son environnement, les suive comme s’il s’agissait de votre demande. La sécurité n’est pas optionnelle — c’est le fondement de toute mise en production.

Le principe du moindre privilège

Votre agent ne devrait avoir accès qu’au strict minimum nécessaire pour accomplir sa tâche. Cela commence par un navigateur isolé, jamais le navigateur principal de l’utilisateur, car celui-ci porte les sessions ouvertes de la messagerie, de la banque et des outils internes. L’agent n’a pas non plus besoin du système de fichiers, sauf d’un dossier dédié où déposer ses téléchargements, ni d’un accès au réseau interne tant qu’aucune application métier n’y est visée, ni de droits administrateur qui transformeraient une erreur de clic en incident système. Enfin, la session doit être éphémère : les données sont supprimées après chaque exécution, ce qui garantit qu’un workflow ne réutilise jamais par accident les cookies ou le cache du précédent.

Sandboxing avec Docker

La méthode recommandée est d’exécuter l’agent dans un conteneur Docker isolé. L’image officielle Playwright fournit déjà le navigateur et ses dépendances système ; vous n’y ajoutez que vos paquets Python et votre code. Le point important se situe en fin de fichier : la création d’un utilisateur agent non privilégié, qui empêche le processus d’écrire hors de son périmètre même s’il est détourné.

FROM mcr.microsoft.com/playwright:v1.49.0-noble

WORKDIR /app

# Installer les dépendances Python
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copier le code de l'agent
COPY src/ ./src/

# Utilisateur non-root
RUN useradd -m agent
USER agent

# Pas d'accès réseau par défaut (configuré au lancement)
CMD ["python", "src/main.py"]

Les garde-fous restants se posent au lancement plutôt que dans l’image, ce qui vous permet de les ajuster par workflow. Le conteneur démarre sans réseau, avec un plafond de mémoire et de CPU, un système de fichiers en lecture seule doublé d’un /tmp volatil, et la clé API transmise par variable d’environnement.

docker run --rm \
  --network=none \
  --memory=2g \
  --cpus=1.5 \
  --read-only \
  --tmpfs /tmp \
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  computer-use-agent

Restrictions réseau ciblées

Couper entièrement le réseau convient à un agent qui travaille sur des fichiers locaux, mais pas à celui qui doit naviguer. Si l’agent doit accéder à certains sites, utilisez un réseau dédié avec un proxy : le réseau Docker est déclaré interne, un proxy y est lancé avec une liste blanche de domaines, et l’agent ne sort que par lui. Tout ce qui n’est pas explicitement autorisé n’existe pas de son point de vue.

# Créer un réseau isolé
docker network create --internal agent-net

# Lancer un proxy qui n'autorise que certains domaines
docker run -d --network agent-net \
  --name agent-proxy \
  -e ALLOWED_DOMAINS="example.com,api.anthropic.com" \
  squid-proxy

# Lancer l'agent avec le proxy
docker run --rm --network agent-net \
  -e HTTP_PROXY="http://agent-proxy:3128" \
  -e HTTPS_PROXY="http://agent-proxy:3128" \
  computer-use-agent

Filtrage des URLs

Une seconde barrière, plus fine, se pose dans le navigateur lui-même. En interceptant toutes les requêtes avec page.route, vous comparez le domaine de chaque appel à votre liste autorisée et vous abandonnez ceux qui n’y figurent pas. Cette couche attrape ce que le proxy laisserait passer, par exemple une redirection déclenchée par un script tiers, et elle produit une trace lisible de chaque blocage — précieuse quand un workflow échoue parce qu’un domaine légitime manquait à la liste.

def setup_url_filter(page, allowed_domains: list[str]):
    """Bloque la navigation vers des domaines non autorisés."""

    def check_navigation(route):
        from urllib.parse import urlparse
        domain = urlparse(route.request.url).hostname

        if any(domain.endswith(d) for d in allowed_domains):
            route.continue_()
        else:
            print(f"  BLOQUÉ : {route.request.url}")
            route.abort()

    page.route("**/*", check_navigation)


# Utilisation
allowed = ["example.com", "api.anthropic.com"]
setup_url_filter(page, allowed)

Protection des données sensibles

Masquer les informations dans les screenshots

Chaque capture d’écran part vers l’API : tout ce qui est visible à l’écran quitte donc votre infrastructure. Si l’écran contient des données sensibles, mots de passe ou numéros de carte, masquez-les avant d’envoyer le screenshot au modèle. Un rectangle noir appliqué sur les zones connues suffit, à condition de connaître leurs coordonnées — d’où l’intérêt de standardiser la résolution du navigateur, comme vu plus tôt dans ce cours.

from PIL import Image, ImageDraw

def mask_sensitive_areas(screenshot_bytes: bytes, regions: list) -> bytes:
    """Masque des zones sensibles dans un screenshot."""
    img = Image.open(io.BytesIO(screenshot_bytes))
    draw = ImageDraw.Draw(img)

    for x, y, w, h in regions:
        draw.rectangle([x, y, x + w, y + h], fill="black")

    buffer = io.BytesIO()
    img.save(buffer, format="PNG")
    return buffer.getvalue()

Ne jamais logger les données sensibles

Le même raisonnement vaut pour les journaux, qui survivent souvent bien plus longtemps que les screenshots. Un filtre de logging appliqué en amont réécrit les motifs à risque avant écriture : les affectations de mot de passe, les clés API au format sk-ant-, et les suites de seize chiffres qui ressemblent à un numéro de carte. Le filtre agit sur le message final, il attrape donc aussi ce que vous avez loggé sans y penser.

import logging
import re

class SensitiveFilter(logging.Filter):
    """Filtre les données sensibles des logs."""

    PATTERNS = [
        (re.compile(r"password['\"]?\s*[:=]\s*['\"]?[^\s,}]+"), "password=***"),
        (re.compile(r"sk-ant-[a-zA-Z0-9-]+"), "sk-ant-***"),
        (re.compile(r"\b\d{16}\b"), "****-****-****-****"),
    ]

    def filter(self, record):
        msg = record.getMessage()
        for pattern, replacement in self.PATTERNS:
            msg = pattern.sub(replacement, msg)
        record.msg = msg
        return True

Limites d’exécution

Reste le risque le plus banal : l’agent qui déraille sans rien casser, mais qui tourne pendant deux heures et brûle votre budget. La classe ExecutionLimits matérialise quatre plafonds — itérations, durée, appels API et coût en dollars — et lève une exception dès que l’un d’eux est atteint. Le comptage des tokens se fait à chaque réponse via track_usage, ce qui permet d’estimer la dépense en cours de route plutôt qu’en découvrant la facture. Réglez ces valeurs par workflow : un test E2E de trente secondes et une saisie de factures d’une heure n’appellent pas les mêmes seuils.

import signal

class ExecutionLimits:
    """Impose des limites d'exécution à l'agent."""

    def __init__(
        self,
        max_iterations: int = 50,
        max_duration_seconds: int = 300,
        max_api_calls: int = 100,
        max_cost_usd: float = 5.0,
    ):
        self.max_iterations = max_iterations
        self.max_duration = max_duration_seconds
        self.max_api_calls = max_api_calls
        self.max_cost = max_cost_usd
        self.api_calls = 0
        self.total_tokens = 0

    def check(self, iteration: int, elapsed: float):
        """Vérifie que les limites ne sont pas dépassées."""
        if iteration >= self.max_iterations:
            raise RuntimeError(
                f"Limite d'itérations atteinte ({self.max_iterations})"
            )
        if elapsed >= self.max_duration:
            raise RuntimeError(
                f"Timeout ({self.max_duration}s)"
            )
        if self.api_calls >= self.max_api_calls:
            raise RuntimeError(
                f"Limite d'appels API atteinte ({self.max_api_calls})"
            )

    def track_usage(self, response):
        """Compte les tokens consommés."""
        self.api_calls += 1
        self.total_tokens += response.usage.input_tokens
        self.total_tokens += response.usage.output_tokens
        cost = self.total_tokens / 1_000_000 * 3  # Estimation
        if cost >= self.max_cost:
            raise RuntimeError(f"Budget dépassé (${cost:.2f})")

Checklist sécurité production

Avant de déployer un agent Computer Use en production, vérifiez :

  • Navigateur dans un conteneur Docker isolé
  • Réseau filtré (domaines autorisés uniquement)
  • Utilisateur non-root dans le conteneur
  • Limites de temps, d’itérations et de coût
  • Logs filtrés (pas de données sensibles)
  • Screenshots non persistés (ou chiffrés)
  • Pas d’accès au système de fichiers hôte
  • Clés API en variables d’environnement (pas dans le code)
  • Mécanisme d’arrêt d’urgence (kill switch)

Le dernier point est celui qu’on oublie le plus souvent, et c’est pourtant le seul qui vous serve quand tous les autres ont échoué. Un kill switch est un moyen d’interrompre l’agent immédiatement, sans attendre qu’il atteigne l’une de ses limites : la présence d’un fichier STOP que la boucle vérifie à chaque itération, un docker stop sur le conteneur, ou un drapeau lu dans une variable partagée. Testez-le avant la mise en production, pas le jour où vous en aurez besoin.

Points clés à retenir

  • Exécutez toujours l’agent dans un environnement sandboxé (Docker recommandé)
  • Filtrez les URLs pour limiter l’accès aux domaines autorisés
  • Masquez les données sensibles dans les screenshots avant de les envoyer au modèle
  • Imposez des limites strictes : itérations, durée, coût, appels API
  • Les logs ne doivent jamais contenir de données sensibles