Skip to content

Exemple : Plugin en Bac à sable (Sandboxed Plugin)

Le mode sandboxed est conçu pour les plugins tiers ou dont vous ne maîtrisez pas le code source. Il garantit une isolation de sécurité maximale.


1. Caractéristiques du mode Sandboxed

  • Processus Isolé : S'exécute dans un sous-processus Python distinct.
  • FS Guard : Accès au système de fichiers restreint (défaut: uniquement data/).
  • AST Scan : Code analysé à la recherche d'imports dangereux (os, sys, subprocess).
  • Ctypes Blocked : Accès aux bibliothèques C natives (libc, ctypes) désactivé.
  • Communication IPC : Échange de données via JSON-RPC uniquement.

2. Le Manifeste (plugin.yaml)

name: risky_calculator
version: 1.0.0
author: Third Party Dev
description: Un plugin de calcul qui peut traiter des données locales
execution_mode: sandboxed
entry_point: src/main.py

# Politique de sécurité du système de fichiers
filesystem:
  allowed_paths: ["data/"]      # Seul ce dossier est accessible
  denied_paths: ["src/", "../"] # Blocage explicite des sources et parents

# Limites de ressources matérielles
resources:
  max_memory_mb: 64             # RAM maximale avant crash (64 Mo)
  timeout_seconds: 5            # Temps de réponse maximal par appel (5 s)

3. Le Code Source (src/main.py)

import json
from pathlib import Path
from xcore.sdk import TrustedBase, ok, error

class Plugin(TrustedBase):
    """
    Plugin Sandboxed démontrant l'isolation FS.
    """

    async def on_load(self) -> None:
        """Accès sécurisé au dossier data/."""
        self.data_dir = Path("data")
        self.data_dir.mkdir(exist_ok=True)
        print("✅ Plugin Sandboxed démarré dans son processus isolé.")

    async def handle(self, action: str, payload: dict) -> dict:
        """
        Point d'entrée pour les appels IPC.
        """
        if action == "save_result":
            # Sauvegarde autorisée dans le dossier data/
            result_file = self.data_dir / "last_result.json"
            result_file.write_text(json.dumps(payload))
            return ok(msg="Résultat sauvegardé dans le bac à sable.")

        if action == "read_secrets":
            # TENTATIVE DE VIOLATION : Lecture hors de data/
            try:
                # Tentative d'accès au fichier de config framework (BLOQUÉ par FS Guard)
                with open("../../xcore.yaml", "r") as f:
                    content = f.read()
                return ok(content=content)
            except PermissionError as e:
                # Retourne l'erreur de violation sans crasher le framework
                return error(str(e), code="security_violation")

        if action == "execute_os":
            # TENTATIVE DE VIOLATION : Import de module interdit
            try:
                import os # BLOQUÉ par l'Import Hook
                return ok(msg=f"OS : {os.name}")
            except (PermissionError, ImportError) as e:
                return error(str(e), code="security_violation")

        return ok(status="running")

4. Test de l'Exemple

Appel d'une action autorisée

curl -X POST http://localhost:8082/plugin/ipc/risky_calculator/save_result \
  -H "X-Plugin-Key: change-me-in-production" \
  -d '{"payload": {"score": 100}}'

# Réponse :
# {"status":"ok","plugin":"risky_calculator","action":"save_result","result":{"status":"ok","msg":"Résultat sauvegardé..."}}

Appel d'une action bloquée (Tentative de violation)

curl -X POST http://localhost:8082/plugin/ipc/risky_calculator/read_secrets \
  -H "X-Plugin-Key: change-me-in-production" \
  -d '{"payload": {}}'

# Réponse :
# {"status":"error","plugin":"risky_calculator","action":"read_secrets","result":{"status":"error","msg":"[sandbox] open('../../xcore.yaml') interdit dans le sandbox","code":"security_violation"}}

Points Clés de l'Exemple

Isolation totale : Les erreurs de sécurité ne propagent pas de crash au Noyau. ✅ FS Guard : Blocage dynamique des accès fichiers non autorisés. ✅ Import Hook : Blocage des modules système Python critiques. ✅ Communication IPC : Le plugin reçoit uniquement les données JSON dont il a besoin.