Skip to main content
Ce document explique le fonctionnement interne de failproofai : comment le système de hooks intercepte les appels d’outils des agents, comment la configuration est chargée et fusionnée, comment les politiques sont évaluées, et comment le tableau de bord surveille l’activité des agents.

Vue d’ensemble

failproofai comporte deux sous-systèmes indépendants :
  1. Gestionnaire de hooks - Un sous-processus CLI rapide que Claude Code invoque à chaque appel d’outil de l’agent. Il évalue les politiques et retourne une décision.
  2. Moniteur d’agents (Tableau de bord) - Une application web Next.js pour surveiller les sessions d’agents et gérer les politiques.
Les deux sous-systèmes partagent des fichiers de configuration dans ~/.failproofai/ et le répertoire .failproofai/ du projet, mais ils s’exécutent en tant que processus séparés et ne communiquent que via le système de fichiers.

Gestionnaire de hooks

Intégration avec Claude Code

Lorsque vous exécutez failproofai policies --install, des entrées comme celle-ci sont écrites dans ~/.claude/settings.json :
Claude Code invoque ensuite failproofai --hook PreToolUse en tant que sous-processus avant chaque appel d’outil, en transmettant une charge utile JSON sur stdin.

Format de la charge utile

Pour les événements PostToolUse, la charge utile contient également tool_result avec la sortie de l’outil. Le gestionnaire impose une limite de 1 Mo sur stdin. Les charges utiles dépassant cette taille sont ignorées et toutes les politiques autorisent implicitement.

Format de réponse

Refus (PreToolUse) :
Refus (PostToolUse) :
Instruction (tout événement sauf Stop) :
Instruction pour l’événement Stop :
  • Code de sortie : 2
  • La raison est écrite sur stderr (pas sur stdout)
Autorisation :
  • Code de sortie : 0
  • stdout vide
Autorisation avec message : allow(message) permet à une politique d’envoyer du contexte informatif à Claude même lorsque l’opération est autorisée. Le gestionnaire de hooks écrit le JSON suivant sur stdout (pas dans un fichier de configuration — c’est la réponse du gestionnaire à Claude Code, tout comme les réponses de refus et d’instruction ci-dessus) :
  • Code de sortie : 0 (l’opération est autorisée)
  • Lorsque plusieurs politiques retournent allow avec un message, leurs messages sont joints par des sauts de ligne en une seule chaîne additionalContext
  • Si aucune politique ne fournit de message, stdout est vide (comme avant)

Pipeline de traitement

src/hooks/handler.ts implémente le pipeline complet :
L’ensemble du processus s’exécute en moins de 100 ms pour les charges utiles classiques, sans aucun appel LLM.

Chargement de la configuration

src/hooks/hooks-config.ts implémente le chargement de configuration à trois niveaux.
Logique de fusion :
  • enabledPolicies - union dédupliquée sur les trois fichiers
  • policyParams - par clé de politique, le premier fichier qui la définit l’emporte entièrement
  • customPoliciesPath - le premier fichier qui la définit l’emporte
  • llm - le premier fichier qui la définit l’emporte
Le tableau de bord web utilise readHooksConfig() (global uniquement) pour la lecture et l’écriture, car il n’est pas invoqué avec un répertoire de travail de projet.

Évaluation des politiques

src/hooks/policy-evaluator.ts exécute les politiques dans l’ordre. Pour chaque politique :
  1. Rechercher le schéma params de la politique (si elle en possède un).
  2. Lire policyParams[policy.name] depuis la configuration fusionnée.
  3. Fusionner les valeurs fournies par l’utilisateur par-dessus les valeurs par défaut du schéma pour produire ctx.params.
  4. Appeler policy.fn(ctx) avec le contexte résolu.
  5. Si le résultat est deny, arrêter immédiatement et retourner cette décision.
  6. Si le résultat est instruct, accumuler le message et continuer.
  7. Si le résultat est allow, passer à la politique suivante.
Après l’exécution de toutes les politiques :
  • Si un deny a été retourné, émettre la réponse de refus.
  • Si des retours instruct ont été collectés, émettre une seule réponse d’instruction avec tous les messages joints.
  • Sinon, émettre une réponse d’autorisation (stdout vide, sortie 0).

Politiques intégrées

src/hooks/builtin-policies.ts définit les 39 politiques intégrées en tant qu’objets BuiltinPolicyDefinition :
Les politiques qui acceptent des params déclarent un PolicyParamsSchema avec les types et valeurs par défaut de chaque paramètre. L’évaluateur de politiques injecte les valeurs résolues dans ctx.params avant d’appeler fn. Les fonctions de politique lisent ctx.params sans vérification de nullité car les valeurs par défaut sont toujours appliquées en premier. La correspondance de motifs au sein des politiques utilise des jetons de commande analysés (argv), et non une correspondance de chaînes brutes. Cela empêche le contournement via l’injection d’opérateurs shell (par exemple, un motif pour sudo systemctl status * ne peut pas être contourné en ajoutant ; rm -rf / à la commande).

Politiques personnalisées

src/hooks/custom-hooks-registry.ts implémente un registre adossé à globalThis :
src/hooks/custom-hooks-loader.ts charge le fichier de politique de l’utilisateur :
  1. Lire customPoliciesPath depuis la configuration ; ignorer si absent.
  2. Résoudre vers un chemin absolu ; vérifier que le fichier existe.
  3. Réécrire toutes les importations from "failproofai" vers le chemin dist réel afin que customPolicies se résolve vers le même registre globalThis.
  4. Réécrire récursivement les importations locales transitives pour assurer la compatibilité ESM.
  5. Écrire des fichiers .mjs temporaires et import() le fichier d’entrée.
  6. Appeler getCustomHooks() pour récupérer les hooks enregistrés.
  7. Nettoyer tous les fichiers temporaires dans un bloc finally.
En cas d’erreur (fichier introuvable, erreur de syntaxe, échec d’importation), l’erreur est consignée dans ~/.failproofai/hook.log et le chargeur retourne un tableau vide. Les politiques intégrées ne sont pas affectées. Les politiques personnalisées sont évaluées après toutes les politiques intégrées. Un deny d’une politique personnalisée court-circuite toujours les politiques personnalisées suivantes (mais toutes les politiques intégrées ont déjà été exécutées à ce stade).

Journalisation de l’activité

Après chaque événement de hook, le gestionnaire ajoute une ligne JSONL à ~/.failproofai/hook-activity.jsonl :
Une ligne par politique ayant pris une décision autre que allow. Les décisions allow ne sont pas journalisées (pour garder le fichier compact).

Architecture du tableau de bord

Le tableau de bord est une application Next.js 16 utilisant l’App Router avec des React Server Components et des Server Actions.
Flux de données :
  • Les composants de page appellent lib/projects.ts et lib/log-entries.ts pour lire les données de projet/session directement depuis le système de fichiers (pas de couche API pour les lectures).
  • La page Politiques utilise des Server Actions pour toutes les mutations (activation, mise à jour des paramètres, installation/suppression).
  • Le visualiseur de session analyse le format de transcript JSONL de Claude et affiche une chronologie des messages et appels d’outils.
Décisions de conception clés :
  • Pas de base de données - tout l’état persistant est dans des fichiers simples (~/.failproofai/, ~/.claude/projects/).
  • Server Actions pour les mutations - pas d’API REST nécessaire pour les opérations CRUD.
  • React Server Components pour les pages de lecture - chargement initial plus rapide, pas de bundle client pour la récupération des données.
  • Composants client uniquement là où l’interactivité est nécessaire (bascules de politiques, recherche d’activité, visualiseur de logs).

Organisation des fichiers