Home » AI » Comment créer un agent local sécurisé avec Gemma 4 ?

Comment créer un agent local sécurisé avec Gemma 4 ?

En donnant à Gemma 4 des outils locaux encadrés, on passe du chatbot à l’agent capable d’explorer, calculer et synthétiser. Le point dur n’est pas l’appel d’outil, mais la sécurité : sandbox, validation des chemins, exécution Python limitée et orchestration contrôlée.

Qu’est-ce qu’un agent local ?

Un agent local est un modèle capable d’utiliser des outils sur la machine hôte pour observer un état, décider d’une action, exécuter cette action, puis intégrer le résultat dans sa réponse.

La différence avec un chatbot classique est nette. Un chatbot répond avec ce qu’il a reçu dans la conversation et ce qu’il a appris pendant son entraînement. Il ne voit pas vos fichiers, ne lance pas de calcul réel et ne modifie rien. Un assistant connecté à une API en lecture seule va plus loin : il peut consulter une source externe, par exemple une base documentaire ou un calendrier, mais il ne peut pas agir. L’agentivité commence quand le modèle peut interroger son environnement local : fichiers, dossiers, scripts, calculs Python, données CSV, logs, configuration projet.

Type Capacité réelle
Chatbot classique Répond à partir du contexte fourni, sans accès direct à la machine.
Assistant en lecture seule Consulte une API ou une base, mais ne modifie rien.
Agent local Demande l’exécution d’outils locaux validés par l’application hôte.

Avec Gemma 4 en local, le modèle reste compact et utile surtout s’il est entouré d’une orchestration d’outils. Le point important : le modèle ne doit pas improviser une action. Il doit produire une demande structurée, souvent en JSON, c’est-à-dire un format texte avec des champs explicites, comme le nom de l’outil et ses paramètres. Le programme hôte valide ensuite cette demande, exécute ou refuse l’action, puis renvoie le résultat au modèle.

Cette séparation est centrale pour la sécurité. Le modèle ne possède pas naturellement l’accès au système de fichiers, à Python ou au terminal. C’est l’application qui lui expose volontairement certaines fonctions. Si une fonction lit un fichier, elle doit contrôler le chemin demandé. OWASP documente le risque de path traversal, une attaque où un chemin comme ../ permet de sortir d’un dossier autorisé. Les documentations Python pathlib et os.path décrivent les fonctions utiles pour résoudre, normaliser et comparer des chemins avant lecture. Références : OWASP Path Traversal, docs.python.org pathlib, docs.python.org os.path.

La documentation Ollama sur les tool calls va dans le même sens : le modèle peut demander un appel de fonction, mais l’application reste responsable de l’exécution et du retour du résultat. Avant de créer des outils puissants, il faut donc comprendre la boucle complète qui relie le modèle, les fonctions Python et les résultats renvoyés au modèle.

Comment fonctionne la boucle d’outils ?

La boucle d’outils fonctionne en exposant des fonctions Python décrites par un schéma JSON, en laissant Gemma 4 demander un appel d’outil, puis en exécutant côté application uniquement les appels validés avant de renvoyer le résultat au modèle.

Le flux reste simple si on le découpe proprement. Le développeur déclare d’abord les outils disponibles. Chaque outil reçoit un name, une description et des parameters typés. Ces paramètres indiquent au modèle ce qu’il peut demander, par exemple une ville, un chemin de fichier ou une expression à calculer. Ensuite, Gemma 4 choisit éventuellement un outil. Rien ne l’oblige à le faire. S’il en demande un, l’orchestrateur intercepte le tool_call, c’est-à-dire la demande structurée d’appel de fonction. L’application vérifie alors le nom, les paramètres, les permissions, puis exécute la fonction locale. Le résultat est ajouté à la conversation, et le modèle produit enfin la réponse finale.

Un schéma JSON conceptuel ressemble à ceci :

{
  "name": "get_weather",
  "description": "Retourne la météo d'une ville.",
  "parameters": {
    "type": "object",
    "properties": {
      "city": { "type": "string" }
    },
    "required": ["city"]
  }
}

Cette architecture est réutilisable parce que le cœur ne change pas. Une API météo, un explorateur de fichiers ou un interpréteur Python suivent le même cycle : déclaration, demande, validation, exécution, retour. Ce qui change, c’est la surface de risque. Une API en lecture seule a un impact limité. Un outil local peut lire des fichiers, lancer du code ou exposer des données sensibles.

TOOLS = {
    "get_weather": get_weather,
    "list_files": list_files,
}

def dispatch_tool(tool_call):
    name = tool_call["name"]
    args = tool_call.get("arguments", {})

    if name not in TOOLS:
        raise ValueError("Outil non autorisé")

    if name == "list_files" and not is_safe_path(args.get("path")):
        raise ValueError("Chemin interdit")

    return TOOLS[name](**args)

Le principe à garder en tête est simple : le modèle propose, l’application dispose. Le texte généré par le modèle ne doit jamais être considéré comme fiable. Les garde-fous doivent vivre dans le code Python : liste blanche d’outils, validation stricte des paramètres, restrictions de chemins, limites de temps, journalisation. Le prompt système aide, mais il ne remplace pas un contrôle d’accès réel.

Composant Rôle Risque principal Garde-fou recommandé
Modèle Propose un outil et des arguments Demande incorrecte ou malveillante Ne jamais exécuter sans validation
Schéma JSON Décrit les outils disponibles Paramètres trop permissifs Types stricts et champs obligatoires
Orchestrateur Intercepte le tool_call Dispatch vers un outil interdit Liste blanche des noms d’outils
Fonction locale Exécute l’action réelle Accès aux fichiers ou au système Permissions minimales et sandbox

Comment sécuriser l’accès aux fichiers ?

On sécurise l’accès aux fichiers en plaçant l’agent dans un répertoire autorisé, puis en résolvant chaque chemin demandé en chemin absolu avant de vérifier qu’il reste dans cette arborescence.

Le risque principal s’appelle path traversal, ou traversée de chemin. Une entrée comme ../../etc/passwd peut sembler être une simple chaîne, mais elle demande au système de remonter dans l’arborescence puis d’ouvrir un fichier situé hors du dossier prévu. OWASP référence cette catégorie de vulnérabilité dans ses ressources sur les attaques de type Path Traversal.

Pour un outil comme list_directory_contents, la règle est simple. L’outil accepte uniquement un chemin relatif, le joint à un dossier de base fixe, résout le chemin réel, vérifie qu’il appartient toujours au dossier autorisé, puis liste seulement les fichiers et dossiers accessibles.

from pathlib import Path

# Répertoire de travail autorisé, défini au démarrage.
SAFE_BASE_DIR = Path("/tmp/gemma-agent-workspace").resolve()

def safe_resolve(relative_path: str) -> Path:
    requested = (SAFE_BASE_DIR / relative_path).resolve()

    try:
        requested.relative_to(SAFE_BASE_DIR)
    except ValueError:
        raise PermissionError("Accès refusé hors du répertoire autorisé")

    return requested

def list_directory_contents(relative_path: str = ".") -> list[str]:
    try:
        directory = safe_resolve(relative_path)

        if not directory.exists():
            raise FileNotFoundError("Dossier introuvable")

        if not directory.is_dir():
            raise NotADirectoryError("Le chemin demandé n'est pas un dossier")

        return sorted(item.name for item in directory.iterdir())

    except PermissionError:
        return ["Erreur : accès refusé"]
    except FileNotFoundError:
        return ["Erreur : dossier introuvable"]
    except NotADirectoryError:
        return ["Erreur : ce chemin n'est pas un dossier"]

Il ne faut pas se contenter de bloquer les chaînes qui contiennent ... Ce filtre paraît rassurant, mais il est fragile. Des liens symboliques peuvent pointer hors du dossier autorisé. Des chemins absolus peuvent ignorer le dossier de base. Des encodages ou des séparateurs différents selon les systèmes peuvent contourner des contrôles naïfs. La résolution canonique avec Path.resolve est plus robuste, car elle demande au système de calculer le chemin réel avant la vérification.

Cette protection ne rend pas l’opération anodine. Lister un dossier reste sensible si ce dossier contient des fichiers .env, des exports clients, des clés privées ou des documents internes. Je recommande donc un répertoire de travail dédié, sans secrets, avec des droits minimaux en lecture et écriture.

  • Base directory fixe : Utiliser un dossier autorisé défini au démarrage.
  • Résolution absolue : Convertir chaque demande en chemin réel avec Path.resolve.
  • Refus hors sandbox : Bloquer tout chemin qui sort de l’arborescence autorisée.
  • Erreurs propres : Gérer PermissionError, FileNotFoundError et NotADirectoryError.
  • Logs : Journaliser les refus sans exposer de chemins sensibles à l’utilisateur.
  • Tests malveillants : Tester ../../etc/passwd, les chemins absolus et les liens symboliques.

Comment encadrer un interpréteur Python ?

Un interpréteur Python pour agent doit être considéré comme un outil à haut risque, car il peut transformer une simple demande en lecture de fichiers, appel réseau, consommation CPU, c’est-à-dire processeur, ou fuite de données.

Son intérêt reste réel. Gemma 4 peut s’en servir pour calculer un résultat, tester une hypothèse, manipuler un petit jeu de données ou vérifier une logique avant de répondre. Sans exécution de code, l’agent raisonne. Avec Python, il peut aussi vérifier. La différence est utile, mais elle change le niveau de risque.

Les limites doivent donc être explicites. Un interpréteur autorisé à tout faire n’est pas un outil, c’est un processus incontrôlé. Les protections minimales sont simples à formuler, moins simples à garantir parfaitement :

  • Un timeout court pour couper une boucle infinie ou un calcul trop long.
  • Une mémoire limitée si l’environnement le permet, pour éviter la saturation de la machine.
  • Un répertoire de travail isolé, sans accès aux dossiers personnels, au projet complet ou aux fichiers système.
  • Aucun accès aux secrets : clés API, jetons, variables d’environnement sensibles, fichiers .env.
  • Une liste explicite de modules autorisés, par exemple math, statistics ou json.
  • Une restriction des imports dangereux, comme os, subprocess, socket, pathlib ou shutil selon le contexte.
  • Un blocage des appels système et du réseau quand ils ne sont pas nécessaires.

Une sandbox Python, c’est-à-dire un environnement d’exécution isolé, n’est pas triviale à construire. Pour un usage sérieux, je privilégie l’isolation par processus séparé, conteneur ou environnement jetable plutôt qu’une simple restriction dans le même interpréteur Python. Le principe est celui du moindre privilège : donner uniquement les droits nécessaires. La défense en profondeur ajoute plusieurs barrières, car une seule protection finit toujours par être contournable.

import subprocess
import tempfile

def run_python(script: str, timeout: int = 2):
    with tempfile.TemporaryDirectory() as workdir:
        result = subprocess.run(
            ["python", "-I", "-c", script],
            cwd=workdir,
            capture_output=True,
            text=True,
            timeout=timeout,
            env={}
        )
        return {
            "stdout": result.stdout,
            "stderr": result.stderr,
            "returncode": result.returncode
        }

Ce code lance Python dans un processus séparé, dans un dossier temporaire, avec un timeout et sans variables d’environnement transmises. Ce n’est pas une sandbox complète. C’est seulement une base plus saine qu’un exec direct dans le processus de l’agent.

L’explorateur de fichiers donne la vue sur l’environnement. L’interpréteur Python donne la capacité de calcul. Ensemble, ils rendent Gemma 4 beaucoup plus utile, mais ils imposent des limites claires pour éviter de confondre agent local et processus libre de tout faire.

Risque Exemple Protection minimale
Lecture de fichiers sensibles. Ouverture d’un fichier .env ou d’une clé SSH. Répertoire isolé et aucun secret accessible.
Consommation excessive. Boucle infinie ou calcul très coûteux. Timeout court et limite mémoire.
Appel réseau non voulu. Envoi de données vers une URL externe. Réseau désactivé par défaut.
Exécution système. Lancement d’une commande via subprocess. Modules dangereux bloqués et isolation par processus ou conteneur.

Comment tester l’agent avant usage ?

Il faut tester l’agent comme une application qui exécute des entrées non fiables, pas comme une simple interface conversationnelle. C’est le bon réflexe dès qu’un modèle peut lire des fichiers, lancer du Python ou appeler un outil local.

Une validation sérieuse doit couvrir les comportements normaux, mais surtout les chemins dangereux. Les tests unitaires vérifient chaque fonction d’outil séparément, sans passer par le modèle. Par exemple, l’explorateur de fichiers doit refuser les chemins hors sandbox, c’est-à-dire hors du dossier isolé autorisé. Le moteur Python doit être testé avec un timeout, donc une durée maximale d’exécution, pour éviter une boucle infinie ou un script trop long.

Zone à tester Ce qu’il faut vérifier
Outils de fichiers Refus de ../, chemins absolus interdits, fichiers inexistants, dossier vide, gros fichiers.
Exécution Python Timeout, mémoire limitée, erreurs contrôlées, absence d’accès réseau si non prévu.
Appels d’outils Refus propre quand le modèle demande un outil non autorisé.
Logs Traçabilité suffisante sans exposer de secrets en clair.

Les décisions du modèle doivent être observables. Chaque appel d’outil devrait journaliser le nom de l’outil demandé, les arguments reçus, le résultat retourné et la réponse finale produite. Attention toutefois aux journaux, ou logs : ils ne doivent pas contenir de clés API, mots de passe, tokens, données personnelles ou contenu métier sensible en clair. Masquer ou tronquer ces valeurs évite de transformer le système de debug en fuite de données.

La progression doit rester prudente. Commencer avec des outils en lecture seule, un dossier de test vide ou rempli de fichiers synthétiques, puis élargir uniquement quand le comportement est compris. Les environnements doivent aussi être séparés : développement pour construire, test pour casser volontairement, production pour exécuter avec des permissions minimales.

  • Le refus des chemins hors sandbox fonctionne systématiquement.
  • Les erreurs sont compréhensibles et ne révèlent pas d’informations sensibles.
  • Aucun secret local n’est accessible par l’agent.
  • Le temps d’exécution est borné par des timeouts.
  • Les résultats sont reproductibles sur des cas simples.
  • Le comportement reste acceptable quand un outil échoue ou répond mal.

L’agent est prêt quand ces critères tiennent sur plusieurs scénarios, y compris malveillants. L’enjeu n’est pas de faire confiance au modèle, mais de concevoir une enveloppe d’exécution qui rend ses actions utiles et limitées.

Et si le vrai sujet était moins l’agent que son périmètre ?

Créer un agent local avec Gemma 4 devient intéressant dès que le modèle peut explorer un dossier, lancer un calcul et intégrer le résultat dans sa réponse. Mais cette puissance vient du code qui l’entoure, pas du modèle seul. La bonne approche consiste à exposer peu d’outils, les décrire clairement, valider chaque argument, isoler les accès et tester les cas hostiles. Un explorateur de fichiers sandboxé et un interpréteur Python limité suffisent déjà à construire des usages concrets. Le bénéfice pour vous : obtenir un agent utile, compréhensible et beaucoup moins risqué à déployer.

FAQ

  • Gemma 4 peut-il accéder seul à mes fichiers locaux ?
    Non. Un modèle n’accède pas seul au système de fichiers. C’est l’application qui lui expose un outil, par exemple une fonction Python listant un dossier. Cette distinction est essentielle : le modèle demande une action, mais le programme hôte décide si elle est autorisée.
  • Pourquoi utiliser une sandbox pour un agent local ?
    Une sandbox limite le périmètre d’action de l’agent. Sans elle, une demande mal formulée ou malveillante pourrait tenter de sortir du dossier prévu, lire des fichiers sensibles ou exploiter des chemins comme ../../. Le but est de rendre l’outil utile sans ouvrir toute la machine.
  • Qu’est-ce que le path traversal ?
    Le path traversal est une technique qui consiste à manipuler un chemin de fichier pour accéder à un emplacement non autorisé. Exemple classique : utiliser ../ pour remonter dans l’arborescence. OWASP classe ce risque parmi les vulnérabilités importantes à traiter côté application.
  • Un prompt système suffit-il à sécuriser les outils ?
    Non. Un prompt peut guider le comportement du modèle, mais il ne remplace pas les contrôles dans le code. La validation des chemins, les refus d’accès, les timeouts et les permissions doivent être implémentés côté application.
  • Faut-il donner un interpréteur Python à un agent IA ?
    Cela peut être très utile pour calculer, tester ou analyser de petites données, mais c’est un outil à haut risque. Il faut l’isoler, limiter le temps d’exécution, restreindre les imports, éviter l’accès réseau et ne jamais l’exécuter avec des droits larges ou près de secrets.

 

 

A propos de l’auteur

Je suis Franck Scandolera, responsable de l’agence webAnalyste et de l’organisme Formations Analytics. J’accompagne des équipes sur le tracking avancé server-side, l’Analytics Engineering, l’automatisation No/Low Code avec n8n, l’intégration de l’IA en entreprise et le SEO/GEO. J’ai travaillé pour des organisations comme Logis Hôtel, Yelloh Village, BazarChic, la Fédération Française de Football ou Texdecor. Si vous voulez cadrer vos projets IA, automatisation ou data avec une approche fiable et opérationnelle, contactez-moi.

Retour en haut
DataMarket AI