Aller au contenu principal
Leçon 14 sur 20 · Claude Code en action

Implémenter un hook

Leçon 14 sur 20 Mis à jour le 4 novembre 2026 Développeur
Sur cette page
  1. Configurer le hook
  2. Implémenter le script
  3. Tester le hook
  4. Ce que le hook ne couvre pas
  5. À vous de jouer
  6. Points clés à retenir

Passons de la théorie au code. Nous allons construire un hook qui empêche Claude de lire ou de modifier les fichiers .env et tout ce qui se trouve dans un dossier secrets/. Il met en œuvre l’intégralité du mécanisme décrit à la leçon précédente : un matcher, un script qui lit stdin, un code de sortie qui tranche. Nous terminerons par la question qu’il faut toujours se poser : ce hook était-il nécessaire, ou une règle de permission suffisait-elle ?

Configurer le hook

Créez le dossier .claude/hooks/ à la racine du projet, puis ajoutez à .claude/settings.json :

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Edit|Write|Grep",
        "hooks": [
          {
            "type": "command",
            "command": "node",
            "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-secrets.js"]
          }
        ]
      }
    ]
  }
}

Trois choix méritent une explication. Le matcher couvre les trois outils de fichiers et Grep : une clé d’API ne fuit pas seulement quand on ouvre le fichier, elle fuit aussi quand une recherche de motif en affiche la ligne (sur macOS et Linux, Grep n’est présent que si vous l’activez, mais le nommer ne coûte rien). La commande est écrite en forme exec, command plus args : Claude Code lance node directement, sans shell, et remplace ${CLAUDE_PROJECT_DIR} par la racine du projet, quel que soit le répertoire courant au moment de l’appel. C’est la forme recommandée dès qu’un chemin est en jeu, et elle rend le hook partageable : commité dans .claude/settings.json, il fonctionne chez chaque collègue sans adaptation.

Implémenter le script

Créez .claude/hooks/protect-secrets.js :

async function main() {
  const chunks = [];
  for await (const chunk of process.stdin) chunks.push(chunk);
  const event = JSON.parse(Buffer.concat(chunks).toString());

  // Chemin visé : file_path pour Read/Edit/Write, path pour Grep
  const target = (event.tool_input?.file_path || event.tool_input?.path || "")
    .replace(/\\/g, "/"); // Windows : normaliser les séparateurs

  const protectedPatterns = ["/.env", "/secrets/"];
  const hit = protectedPatterns.find((p) => target.includes(p));

  if (hit) {
    console.error(
      `Accès refusé : ${target} correspond au motif protégé "${hit}". ` +
      "Lis la variable d'environnement correspondante plutôt que le fichier."
    );
    process.exit(2);
  }
}

main();

Quatre moments structurent ce script. Il commence par accumuler l’intégralité de stdin avant de l’analyser, parce que les données arrivent par fragments et qu’un JSON.parse sur un fragment échouerait. Il extrait le chemin visé en essayant file_path puis path, les outils ne nommant pas leur argument de la même façon, et normalise les barres obliques inverses pour que la comparaison tienne sous Windows. Il compare ensuite à une liste de motifs : parce que les chemins arrivent absolus, /.env attrape .env à n’importe quelle profondeur sans attraper environment.ts. S’il y a correspondance, il écrit une explication sur stderr et sort avec le code 2 : Claude reçoit le message et comprend que l’opération a été bloquée, avec une alternative. Sinon, la fonction se termine normalement, le processus sort avec 0, et l’appel suit son cours. Le silence vaut absence d’objection.

Tester le hook

Les hooks des fichiers de réglages sont relus par Claude Code quand le fichier change ; si vous veniez de créer le dossier .claude/, relancez la session par prudence. Vérifiez avec /hooks que le hook apparaît sous PreToolUse, puis demandez :

Lis le contenu du fichier .env

Claude tente l’opération Read, le hook l’intercepte, et Claude vous explique qu’il n’a pas pu lire le fichier, souvent en proposant de lire la variable d’environnement. Testez aussi une modification (« ajoute une ligne à .env ») et une recherche (« cherche DATABASE_URL dans le projet ») : la même protection s’applique, ce qui valide les autres termes du matcher. Pour voir le détail d’un hook qui ne se comporte pas comme prévu, lancez claude --debug et lisez ~/.claude/debug/<session-id>.txt : chaque exécution y laisse une ligne avec son code de sortie et sa durée.

Ce que le hook ne couvre pas

Testez maintenant : « Affiche .env avec cat. » L’appel passe par l’outil Bash, que notre matcher ne surveille pas, et le fichier s’affiche. C’est la limite de tout hook de ce type : il ne voit que les outils qu’il nomme. Vous pourriez ajouter Bash au matcher et analyser tool_input.command, mais reconnaître toutes les façons de lire un fichier dans un shell est un puits sans fond.

Pour ce besoin précis, la solution intégrée est meilleure. Une règle de permission deny sur Read s’applique aux outils de fichiers, aux mentions @, et aux commandes de lecture que Claude Code reconnaît dans Bash (cat, head, tail, sed, les redirections) ; elle bloque aussi Edit et Write sur le même chemin, dans tous les modes de permission :

{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)"]
  }
}

Elle ne couvre pas un script Python ou Node qui ouvrirait le fichier lui-même ; pour une protection au niveau du système, c’est le bac à sable (sandboxing) qui bloque tous les processus, avec son réglage sandbox.credentials dédié aux fichiers d’identifiants. Le bon réflexe est donc gradué : une règle deny pour les chemins connus, le bac à sable pour une garantie forte, et un hook quand la décision demande de la logique que les règles n’expriment pas, comme n’autoriser la lecture qu’à certaines heures, journaliser chaque tentative, ou vérifier le contenu d’une modification avant de l’accepter.

Onglet Deny du panneau /permissions de Claude Code avec deux règles : Read(./.env) et Read(./secrets/**)
Capture réelle de Claude Code 2.1.317 (4 novembre 2026) : l'onglet Deny de /permissions, où les règles de refus s'ajoutent sans ouvrir settings.json.

À vous de jouer

  1. Mettez en place la configuration et le script ci-dessus, puis déroulez les trois tests (lecture, modification, recherche) et le contre-test avec cat.
  2. Ajoutez la règle deny et refaites le contre-test : cat .env est maintenant bloqué.
  3. Étendez le script pour journaliser chaque tentative bloquée dans .claude/blocked.log avec l’heure et le nom de l’outil (event.tool_name).
  4. Commitez .claude/settings.json et .claude/hooks/, clonez le dépôt dans un autre dossier et vérifiez que le hook fonctionne sans modification.

Sources officielles : Automate actions with hooks · Hooks reference · Configure permissions · Configure the sandboxed Bash tool

Points clés à retenir

  • Forme exec (command + args) et ${CLAUDE_PROJECT_DIR} rendent un hook partageable, sans chemin absolu propre à une machine
  • Le script accumule stdin, extrait le chemin (file_path ou path), normalise les séparateurs, compare, et sort avec 2 et un message sur stderr pour bloquer
  • Un hook ne voit que les outils de son matcher : cat .env par Bash lui échappe
  • Pour protéger des chemins, une règle deny Read(…) est plus complète ; le bac à sable donne la garantie système ; le hook sert quand il faut de la logique
  • claude --debug journalise chaque exécution de hook avec son code de sortie