Aller au contenu
Formation Claude

Tapez au moins deux lettres.

Commencer

Permissions, hooks et garde-fous

Les mécanismes qui contraignent réellement Claude Code : règles de permission, hooks de cycle de vie, isolation. Ce qui est appliqué par le client, quoi que le modèle décide.

12 min de lecturePartie 5 — Claude CodeTechnique

À la fin de ce chapitre, vous saurez

  • Écrire des règles de permission qui autorisent le travail sans ouvrir la porte
  • Choisir le bon niveau de configuration selon ce qui doit être partagé
  • Poser des hooks qui garantissent un comportement, quoi que Claude décide
  • Isoler une session qui doit tourner sans supervision
Dans ce chapitre
  1. Les fichiers de configuration
  2. Les règles de permission
  3. Les hooks
  4. Un hook qui bloque
  5. Un hook qui referme la boucle
  6. Un hook qui injecte du contexte
  7. Isoler une session
  8. Ce qu’il faut retenir

Le chapitre précédent a posé la distinction : un CLAUDE.md exprime une intention, une permission impose une contrainte. Ce chapitre traite des mécanismes qui contraignent réellement.

C’est aussi le chapitre qui permet de travailler vite sans travailler dangereusement — les deux ne s’opposent que si l’on n’a pas configuré les garde-fous.

Les fichiers de configuration

Quatre niveaux, dans l’ordre de priorité décroissante.

NiveauEmplacementPartagéUsage
Politique géréeDéployé par l’informatique (/etc/claude-code/managed-settings.json sur Linux)OuiNon contournable
Local.claude/settings.local.jsonNon, ignoré par gitVos réglages sur ce projet
Projet.claude/settings.jsonOui, versionnéRéglages de l’équipe
Utilisateur~/.claude/settings.jsonNonVos réglages, tous projets

La politique gérée l’emporte sur tout et ne se contourne pas.

Une exception importante : les règles de permission fusionnent entre les niveaux au lieu de s’écraser. Une interdiction posée au niveau projet reste active même si votre fichier local autorise largement.

/status affiche les fichiers effectivement chargés et les règles actives. Un fichier au JSON invalide n’apparaît pas — c’est la première chose à vérifier quand une règle semble ignorée.

Les règles de permission

Trois listes, une syntaxe.

json
{
  "permissions": {
    "allow": [
      "Bash(npm run test *)",
      "Bash(npm run lint)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Read(./src/**)",
      "Edit(./src/**)"
    ],
    "ask": [
      "Bash(git push *)",
      "Bash(npm publish *)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Edit(./migrations/**)",
      "Bash(rm -rf *)",
      "Bash(curl *)"
    ]
  }
}

Trois choses à retenir sur la syntaxe.

Le nom de l’outil, puis un motif entre parenthèses. Bash, Read, Edit, Write, et les outils apportés par les serveurs MCP sous la forme mcp__serveur__outil.

Les motifs acceptent * et **. Read(./src/**) couvre toute l’arborescence.

Le refus gagne toujours. C’est ce qui permet d’autoriser largement le travail courant tout en fermant quelques portes précises.

Les hooks

Un hook est une commande exécutée automatiquement à un point précis du cycle de vie, quelle que soit la décision du modèle. C’est la différence de nature avec une instruction.

Ils se déclarent dans les mêmes fichiers de réglages, avec une structure à trois niveaux : l’événement, un groupe de filtrage, les gestionnaires.

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write \"$CLAUDE_FILE_PATHS\"",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Les événements les plus utiles au quotidien :

ÉvénementQuandUsage typique
SessionStartOuverture ou reprise de sessionInjecter le contexte : branche, tickets en cours
UserPromptSubmitAvant traitement d’un messageAjouter du contexte, refuser un message
PreToolUseAvant l’exécution d’un outilBloquer une action
PostToolUseAprès le succès d’un outilFormater, relancer les tests, journaliser
StopFin de la réponseVérification finale, notification
PreCompactAvant condensation du contexteSauvegarder ce qui ne doit pas se perdre

Le mécanisme de blocage tient au code de sortie : un hook qui sort en 2 bloque l’action, et son stderr devient le message d’erreur transmis à Claude. Un code 0 laisse passer ; un JSON en sortie permet un contrôle plus fin.

Un hook qui bloque

L’exemple canonique : empêcher toute écriture hors du dépôt.

bash
#!/usr/bin/env bash
# .claude/hooks/protege-hors-depot.sh
# Refuse toute écriture dont le chemin sort du répertoire du projet.

set -euo pipefail

entree=$(cat)
chemin=$(printf '%s' "$entree" | jq -r '.tool_input.file_path // empty')

[ -z "$chemin" ] && exit 0

racine=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
absolu=$(cd "$(dirname "$chemin")" 2>/dev/null && pwd -P)/$(basename "$chemin")

case "$absolu" in
  "$racine"/*) exit 0 ;;
  *)
    echo "Écriture refusée hors du dépôt : $absolu" >&2
    exit 2
    ;;
esac
json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/protege-hors-depot.sh" }
        ]
      }
    ]
  }
}

Ce que ce hook garantit et qu’aucune instruction ne garantit : quelle que soit la façon dont Claude formule son action, elle est refusée.

Un hook qui referme la boucle

L’usage le plus rentable au quotidien : relancer la vérification après chaque modification, pour que Claude voie immédiatement ce qu’il a cassé.

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "npm run lint:changed --silent || true",
            "timeout": 60,
            "async": true
          }
        ]
      }
    ]
  }
}

Le async évite de bloquer la session sur une vérification longue.

Un hook qui injecte du contexte

json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo \"Branche : $(git branch --show-current) — dernier commit : $(git log -1 --format=%s)\""
          }
        ]
      }
    ]
  }
}

La sortie standard d’un hook SessionStart est ajoutée au contexte. C’est une manière élégante de donner à Claude l’état courant sans l’écrire dans CLAUDE.md.

Isoler une session

Quand une session doit tourner sans supervision — traitement long, intégration continue, travail nocturne — la bonne réponse n’est pas d’élargir les permissions : c’est de réduire ce qui peut être atteint.

Trois niveaux, du plus léger au plus solide.

Un dépôt jetable. Un clone frais, sans secrets, sans accès aux environnements. Ce qui peut être cassé se jette.

Un worktree git dédié. git worktree add ../travail-auto isole les modifications de votre copie de travail sans dupliquer le dépôt.

Un conteneur. L’isolation réelle : système de fichiers, réseau et identifiants contrôlés. C’est le seul contexte où un mode sans confirmation se justifie.

Une combinaison qui marche bien : conteneur + permissions larges à l’intérieur + réseau restreint en sortie. Claude travaille librement dans une boîte dont les parois sont sûres.

Une configuration d’équipe qui tient

La situation

Une équipe de six personnes sur une application qui manipule des données clients. Il faut que Claude Code soit utilisable au quotidien sans que quiconque puisse, même par accident, exposer un secret ou toucher aux migrations.

Le fichier de projet, versionné

.claude/settings.json :

json
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": [
      "Bash(pnpm run *)",
      "Bash(pnpm test *)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Read(./src/**)",
      "Read(./tests/**)",
      "Edit(./src/**)",
      "Edit(./tests/**)"
    ],
    "ask": [
      "Bash(git push *)",
      "Bash(gh pr create *)"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./**/credentials*)",
      "Edit(./migrations/**)",
      "Edit(./.github/workflows/**)",
      "Bash(curl *)",
      "Bash(wget *)",
      "Bash(psql *)"
    ]
  },
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "pnpm exec prettier --write \"$CLAUDE_FILE_PATHS\"", "timeout": 30 }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/refuse-prod.sh" }
        ]
      }
    ]
  }
}

Le hook refuse-prod.sh refuse toute commande dont la ligne contient une chaîne de connexion de production, quelle que soit sa forme.

Le fichier local, personnel

Chacun ajoute dans .claude/settings.local.json ce qui lui est propre :

json
{
  "permissions": {
    "allow": ["Bash(docker compose *)"]
  }
}

Les autorisations s’ajoutent, les interdictions du projet restent actives — c’est la règle de fusion.

La vérification

Chacun lance /status et vérifie que les trois fichiers sont chargés. Puis quelqu’un demande explicitement à Claude de lire .env, pour confirmer que le refus fonctionne.

Ce que ça change

Le mode d’acceptation des éditions devient utilisable : Claude travaille vite, et les trois choses qui pouvaient mal tourner sont fermées par le client, pas par une phrase dans un fichier Markdown.

Ce qu’il faut retenir

Vérifier que c’est passé

3 questions. Rien n’est enregistré, rien n’est envoyé : c’est pour vous.

Question 1

Une règle allow et une règle deny correspondent toutes les deux à une action. Que se passe-t-il ?

Voir la réponse

A — Le refus l’emporte

Le refus est toujours prioritaire. C’est ce qui permet d’écrire des autorisations larges pour le travail courant tout en interdisant précisément quelques chemins ou commandes sensibles.

Question 2

Quel mécanisme garantit qu’une commande s’exécute après chaque modification de fichier ?

Voir la réponse

A — Un hook PostToolUse

Une instruction est suivie la plupart du temps ; un hook s’exécute à un point fixe du cycle de vie, quoi que Claude décide. Pour un formatage automatique après chaque édition, c’est le seul mécanisme fiable.

Question 3

Où placer une règle de permission qui doit valoir pour toute l’équipe ?

Voir la réponse

B — Dans .claude/settings.json, versionné avec le dépôt

Le fichier de projet est versionné et donc partagé. Le fichier local est ignoré par git et reste sur votre machine ; le fichier utilisateur vaut pour tous vos projets mais pour vous seul.

Le garde-fou qui compte chez vous

Identifiez, dans un projet réel, l’action que vous ne voulez sous aucun prétexte voir exécutée automatiquement : une commande de déploiement, une écriture dans un dossier de migrations, la lecture d’un fichier de secrets. Écrivez la règle deny correspondante dans .claude/settings.json, puis vérifiez avec /status qu’elle est bien chargée, et demandez à Claude de faire précisément cette chose pour confirmer qu’il est bloqué.

Un indice, si vous bloquez

Testez vraiment. Une règle mal écrite — un chemin relatif au lieu d’un glob, une majuscule oubliée sur le nom de l’outil — ne bloque rien et donne un faux sentiment de sécurité.