Passer au contenu principal
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 d’agent. Il évalue les politiques et retourne une décision.
  2. Moniteur d’agent (Tableau de bord) - Une application web Next.js pour surveiller les sessions d’agent et gérer les politiques.
Les deux sous-systèmes partagent les fichiers de configuration dans ~/.failproofai/ et le répertoire .failproofai/ du projet, mais s’exécutent en tant que processus séparés et ne communiquent qu’à travers 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 :
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
Claude Code invoque ensuite failproofai --hook PreToolUse en tant que sous-processus avant chaque appel d’outil, en transmettant un payload JSON sur stdin.

Format du payload

{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl",
  "cwd": "/home/user/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "sudo apt install nodejs" }
}
Pour les événements PostToolUse, le payload contient également tool_result avec la sortie de l’outil. Le gestionnaire impose une limite de 1 Mo sur stdin. Les payloads dépassant cette limite sont ignorés et toutes les politiques autorisent implicitement.

Format de la réponse

Refus (PreToolUse) :
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
Refus (PostToolUse) :
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
Instruction (tout événement sauf Stop) :
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
Instruction sur l’événement Stop :
  • Code de sortie : 2
  • Raison écrite sur stderr (pas sur stdout)
Autorisation :
  • Code de sortie : 0
  • stdout vide
Autorisation avec message : allow(message) permet à une politique d’envoyer un 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 — il s’agit de la réponse du gestionnaire à Claude Code, tout comme les réponses de refus et d’instruction ci-dessus) :
// Written to stdout by the hook handler process
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
  • 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 auparavant)

Pipeline de traitement

src/hooks/handler.ts implémente le pipeline complet :
stdin JSON
  → analyser le payload (max 1 Mo)
  → extraire les métadonnées de session (session_id, cwd, tool_name, tool_input, etc.)
  → readMergedHooksConfig(cwd)    ← fusionne la config projet + locale + globale
  → enregistrer les politiques intégrées activées avec les paramètres résolus
  → charger les politiques personnalisées depuis customPoliciesPath (si défini)
  → enregistrer les politiques personnalisées dans le registre de politiques
  → évaluer toutes les politiques (intégrées en premier, puis personnalisées)
      → le premier refus court-circuite
      → les décisions d'instruction s'accumulent
      → les messages d'autorisation s'accumulent
  → écrire la décision JSON sur stdout
  → persister l'événement dans ~/.failproofai/hook-activity.jsonl
  → quitter
L’ensemble du processus s’exécute en moins de 100 ms pour les payloads typiques, sans aucun appel LLM.

Chargement de la configuration

src/hooks/hooks-config.ts implémente le chargement de configuration à trois portées.
[1] {cwd}/.failproofai/policies-config.json        ← projet   (priorité la plus haute)
[2] {cwd}/.failproofai/policies-config.local.json  ← local
[3] ~/.failproofai/policies-config.json             ← global   (priorité la plus basse)
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 le définit l’emporte
  • llm - le premier fichier qui le 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 sur 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, s’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, code de sortie 0).

Politiques intégrées

src/hooks/builtin-policies.ts définit les 26 politiques intégrées sous forme d’objets BuiltinPolicyDefinition :
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
Les politiques qui acceptent des params déclarent un PolicyParamsSchema avec les types et les 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 à l’intérieur des politiques utilise des tokens 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 basé sur globalThis :
const REGISTRY_KEY = "__failproofai_custom_hooks__";

export const customPolicies = {
  add(hook: CustomHook): void { ... }
};

export function getCustomHooks(): CustomHook[] { ... }
export function clearCustomHooks(): void { ... }  // used in tests
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 le chemin absolu ; vérifier que le fichier existe.
  3. Réécrire tous les imports from "failproofai" vers le chemin dist réel afin que customPolicies résolve vers le même registre globalThis.
  4. Réécrire récursivement les imports locaux transitifs 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’import), 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 quand même 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 dans ~/.failproofai/hook-activity.jsonl :
{
  "timestamp": "2026-04-06T12:34:56.789Z",
  "sessionId": "abc123",
  "eventType": "PreToolUse",
  "toolName": "Bash",
  "policyName": "block-sudo",
  "decision": "deny",
  "reason": "sudo command blocked by failproofai",
  "durationMs": 12
}
Une ligne par politique ayant pris une décision autre que allow. Les décisions d’autorisation ne sont pas journalisées (pour maintenir la taille du fichier réduite).

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.
app/
  layout.tsx                  ← Mise en page racine (thème, télémétrie, nav)
  projects/page.tsx           ← Composant serveur : liste tous les projets Claude
  project/[name]/page.tsx     ← Composant serveur : liste les sessions d'un projet
  project/[name]/session/
    [sessionId]/page.tsx      ← Composant serveur : affiche le visualiseur de session
  policies/page.tsx           ← Composant client : gestion des politiques + journal d'activité
  actions/
    get-hooks-config.ts       ← Lire la config + la liste des politiques
    update-hooks-config.ts    ← Activer/désactiver une politique
    update-policy-params.ts   ← Mettre à jour les paramètres d'une politique
    get-hook-activity.ts      ← Paginer/rechercher dans le journal d'activité
    install-hooks-web.ts      ← Installer/supprimer les hooks depuis le navigateur
  api/
    download/[project]/[session]/route.ts   ← Exporter une session en ZIP/JSONL
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/désactivation, 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 des 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 - aucune 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 politique, recherche d’activité, visualiseur de journal).

Organisation des fichiers

failproofai/
├── bin/
│   └── failproofai.mjs           # Routeur CLI (hook / dashboard / install / etc.)
├── src/hooks/
│   ├── handler.ts                # Pipeline d'événements de hook
│   ├── builtin-policies.ts       # 26 définitions de politiques
│   ├── policy-evaluator.ts       # Moteur d'exécution des politiques
│   ├── policy-registry.ts        # Enregistrement et recherche de politiques
│   ├── policy-types.ts           # Interfaces TypeScript
│   ├── hooks-config.ts           # Chargement de configuration multi-portée
│   ├── custom-hooks-registry.ts  # Registre de hooks basé sur globalThis
│   ├── custom-hooks-loader.ts    # Chargeur ESM pour les hooks JS utilisateur
│   ├── manager.ts                # Opérations install / remove / list
│   ├── install-prompt.ts         # Invite de sélection interactive des politiques
│   ├── hook-logger.ts            # Journalisation vers hook.log
│   ├── hook-activity-store.ts    # Persistance de l'activité dans hook-activity.jsonl
│   └── llm-client.ts             # Client API LLM (pour les politiques pilotées par IA)
├── app/                          # Tableau de bord Next.js (pages + server actions)
├── lib/                          # Utilitaires partagés
│   ├── projects.ts               # Enumération des projets Claude depuis le système de fichiers
│   ├── log-entries.ts            # Analyse du format JSONL des transcripts Claude
│   ├── paths.ts                  # Résolution des chemins système
│   └── ...
├── components/                   # Composants React UI partagés
├── contexts/                     # Fournisseurs de contexte React (thème, actualisation automatique, télémétrie)
├── examples/                     # Exemples de fichiers de hooks personnalisés
└── __tests__/                    # Tests unitaires et E2E