Aller au contenu
Développement medium

Claude Code CLI : settings.json avancé et permissions par projet

20 min de lecture

Logo Claude Code - settings.json avancé et permissions par projet

Après la série débutant, vos sessions sont cadrées mais chaque projet redemande les mêmes autorisations. Ce guide pose une configuration durable via settings.json : vous verrouillez les permissions sensibles, pré-autorisez les commandes répétitives et partagez un socle commun avec votre équipe. Tout est appliqué sur lab-claude et vérifié avec /status.

  • Les quatre fichiers de configuration et les cinq niveaux de priorité
  • Comment Claude Code décide : type d'outil, ordre deny puis ask puis allow
  • Les six modes de permission, de plan à bypassPermissions
  • Poser chaque niveau concrètement, du fichier projet aux réglages administrés
  • Écrire une règle juste du premier coup : outil, portée, liste, forme du motif
  • Vérifier la configuration active avec /permissions et /status

Claude Code lit la configuration depuis quatre emplacements, du plus spécifique au plus général :

ScopeFichierUsageSuivi git
Managedmanaged-settings.json (système)Politique d'entreprise imposéeN/A
Local.claude/settings.local.jsonPréférences personnelles du projetIgnoré
Project.claude/settings.jsonRègles partagées de l'équipeSuivi
User~/.claude/settings.jsonPréférences globales utilisateurN/A

La documentation officielle donne cinq niveaux de priorité, parce que les arguments de ligne de commande s'intercalent entre le niveau administré et le fichier local :

  1. Managed, le plus fort : « can't be overridden by anything »
  2. Arguments de ligne de commande, le temps d'une session
  3. Local, qui l'emporte sur le projet et l'utilisateur
  4. Project
  5. User, qui s'applique quand rien d'autre ne définit le réglage

Les permissions font exception à cette hiérarchie. Elles ne s'écrasent pas, elles fusionnent : « Permission rules behave differently because they merge across scopes rather than override ». Une règle du fichier utilisateur coexiste donc avec celles du projet au lieu d'être remplacée. C'est ce qui explique qu'un fichier de permissions ne fasse que grossir avec le temps, sujet traité dans sécuriser son settings.json.

Comment Claude Code décide d'exécuter une commande

Section intitulée « Comment Claude Code décide d'exécuter une commande »

Avant d'écrire la moindre règle, il faut comprendre ce qui se passe quand Claude veut lancer quelque chose. Beaucoup de configurations ratées viennent d'une mauvaise représentation de ce cycle, pas d'une erreur de syntaxe.

Première étape, le type d'outil. Tous ne sont pas soumis au même régime, et c'est la source de confusion la plus fréquente.

Type d'outilExempleApprobation demandée
Lecture seulelire un fichier, GrepNon, dans les répertoires de travail
Commande shellBashOui, sauf un jeu de commandes en lecture seule
Modification de fichierEdit, WriteOui

Deuxième étape, l'évaluation des règles. Elle suit un ordre strict, et la documentation insiste sur un point contre-intuitif : « Rules are evaluated in order: deny, then ask, then allow. The first match in that order determines the outcome, and rule specificity doesn't change the order ».

Autrement dit, un deny large l'emporte toujours sur un allow précis. Une règle Bash(aws *) en deny bloque Bash(aws s3 ls) même si cette dernière figure explicitement en allow. Une liste deny ne peut donc pas porter d'exceptions : il n'existe aucun moyen de dire « tout aws sauf aws s3 ls ».

Le même principe vaut entre ask et allow : une règle ask qui correspond déclenche la question, même si un allow plus spécifique correspond aussi.

Troisième étape, le mode de permission, qui décide de ce qui se passe quand aucune règle ne correspond.

Le defaultMode est le réglage qui change le plus le quotidien, et le guide serait incomplet en le réduisant à une ligne de tableau. Six valeurs existent.

ModeComportement
defaultDemande à chaque action non couverte par une règle. Affiché Manual dans l'interface depuis la version 2.1.200
acceptEditsAccepte automatiquement les modifications de fichiers, continue de demander pour le reste
planClaude lit et explore avec des commandes en lecture seule, sans modifier vos sources
autoApprouve automatiquement, avec des contrôles de sécurité en arrière-plan qui vérifient la cohérence avec votre demande
dontAskRefuse automatiquement tout ce qui n'est pas pré-approuvé, au lieu de demander
bypassPermissionsPasse outre les demandes, sauf les règles ask explicites

Les deux derniers sont des extrêmes symétriques et méritent une lecture attentive. dontAsk durcit : ce qui n'est pas prévu est refusé, sans interruption. bypassPermissions ouvre, et la documentation l'assortit d'un avertissement net.

Commencez toujours avec $schema pour activer l'autocomplétion et la validation dans votre éditeur.

{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [],
"deny": [],
"ask": []
},
"env": {},
"includeCoAuthoredBy": false,
"cleanupPeriodDays": 30
}

Les clés les plus utiles au quotidien :

CléEffet
permissions.allowCommandes pré-autorisées, pas de prompt
permissions.denyCommandes toujours refusées, non contournables
permissions.askCommandes qui demandent confirmation à chaque fois
envVariables d'environnement injectées dans chaque session
includeCoAuthoredByActive ou coupe l'attribution Co-Authored-By: Claude
cleanupPeriodDaysRétention des transcriptions de conversation
defaultModeMode de permission par défaut (default, acceptEdits, plan)

Les quatre fichiers n'ont ni le même emplacement, ni le même mode de création, ni le même sort vis-à-vis de git. Voici comment poser chacun, dans l'ordre où on en a besoin.

  1. Le niveau projet, partagé par l'équipe. C'est celui qu'on crée en premier, parce qu'il porte les règles qui valent pour tout le monde.

    Fenêtre de terminal
    mkdir -p .claude
    cat > .claude/settings.json <<'EOF'
    {
    "$schema": "https://json.schemastore.org/claude-code-settings.json",
    "permissions": {
    "allow": ["Bash(npm run lint:*)", "Bash(npm test:*)"],
    "ask": ["Bash(git push:*)"],
    "deny": ["Read(./.env)", "Bash(rm -rf:*)"]
    }
    }
    EOF
    git add .claude/settings.json

    Ce fichier se commite. Il est le contrat du dépôt, au même titre qu'un .editorconfig.

  2. Le niveau local, personnel au dépôt. Il sert à ce qui dépend de votre machine et n'a rien à faire dans le dépôt partagé.

    Fenêtre de terminal
    cat > .claude/settings.local.json <<'EOF'
    {
    "permissions": {
    "allow": ["Bash(podman:*)"]
    }
    }
    EOF

    Créé par Claude Code, ce fichier est automatiquement ajouté au .gitignore. Créé à la main, comme ci-dessus, il ne l'est pas : vérifiez-le, sinon vos règles personnelles partiront sur le dépôt.

    Fenêtre de terminal
    git check-ignore -v .claude/settings.local.json
  3. Le niveau utilisateur, valable pour tous vos projets. C'est la place des règles qui n'ont aucun rapport avec un dépôt particulier.

    Fenêtre de terminal
    mkdir -p ~/.claude
    cat > ~/.claude/settings.json <<'EOF'
    {
    "permissions": {
    "deny": [
    "Read(~/.ssh/**)",
    "Read(~/.aws/credentials)",
    "Read(~/.claude/.credentials.json)"
    ]
    }
    }
    EOF

    Un deny sur vos secrets est le meilleur usage de ce niveau : il vous suit partout, quel que soit le projet ouvert.

  4. Le niveau administré, imposé par l'organisation. Il se déploie par l'outillage de gestion de parc, à un emplacement système qui dépend de l'OS.

    SystèmeChemin
    Linux et WSL/etc/claude-code/managed-settings.json
    macOS/Library/Application Support/ClaudeCode/managed-settings.json
    WindowsC:\Program Files\ClaudeCode\managed-settings.json

    Ce niveau ne se contourne pas. Il porte aussi les verrous de politique, comme disableBypassPermissionsMode qui interdit le mode le plus permissif.

Une règle mal placée ou mal formée ne produit aucune erreur, elle est simplement sans effet. Quatre questions permettent de la poser juste du premier coup.

Quel outil ? Le nom qui précède la parenthèse doit être celui de l'outil réellement utilisé : Bash pour une commande shell, Read pour une lecture, Edit et Write pour une modification, WebFetch pour une requête réseau. Écrire Bash(...) pour protéger un fichier ne protège rien.

Quelle portée ? Une commande stable et utile à toute l'équipe va dans le fichier de projet. Une commande liée à votre poste va dans le fichier local. Une protection de vos secrets va dans le fichier utilisateur.

Quelle liste ? deny pour ce qui ne doit jamais passer, ask pour ce qui doit rester sous votre œil, allow pour ce qui est répétitif et sans risque. Rappel du cycle : deny gagne toujours, quelle que soit la précision de la règle allow en face.

Quelle forme de motif ? C'est là que se jouent la plupart des échecs.

IntentionMotif correctErreur fréquente
Une commande exacteBash(git status)
Un préfixe et ses argumentsBash(npm run:*)Bash(npm run *)
Un fichier précisRead(./.env)
Un dossier et son contenuRead(./secrets/**)Read(./secrets)
Un domaine réseauWebFetch(domain:github.com)WebFetch(github.com)

Une fois la règle écrite, vérifiez qu'elle est bien active plutôt que de le supposer. La commande /permissions liste toutes les règles et le fichier d'où chacune provient, ce qui tranche immédiatement entre une règle absente et une règle présente mais mal formée.

/permissions

Les règles utilisent une syntaxe simple Outil(motif) :

Bash(uv run ruff check:*) -> préfixe "uv run ruff check" + n'importe quels arguments
Bash(git status) -> commande exacte, sans argument
Read(./.env) -> lecture d'un fichier précis
Read(./secrets/**) -> lecture récursive d'un dossier
Edit(src/**) -> édition d'un chemin via motif
WebFetch(domain:github.com) -> accès réseau restreint à un domaine

Règles importantes :

  • La forme canonique pour « préfixe + arguments libres » est Bash(prefix:*) avec deux-points (Bash(uv run:*) autorise toutes les sous-commandes uv run)
  • Les motifs deny ont priorité sur allow au sein d'un même scope
  • deny au niveau Managed ne peut jamais être contourné

C'est le piège le plus coûteux, et il ne se voit pas. Une règle réduite au nom de l'outil, sans parenthèses, autorise cet outil en entier. La documentation l'énonce comme une recette : « To run all Bash commands without prompts except for a few you want blocked, add "Bash" to your allow list ».

{
"permissions": {
"allow": ["Bash"]
}
}

Cette seule ligne rend inutiles toutes les autres règles Bash(...) du fichier, aussi précises soient-elles. Sur une configuration réelle auditée, 349 des 386 règles étaient dans ce cas : elles ne protégeaient plus rien depuis le jour où un Bash nu avait été ajouté.

Le même effet vaut pour Read, Edit, Write, Glob, Grep, et pour un serveur MCP entier comme mcp__github, qui couvre tous ses outils.

La forme :* n'est reconnue qu'en fin de motif. Dans Bash(git:* push), le deux-points est traité comme un caractère littéral et la règle ne correspondra à aucune commande git.

Claude Code retire par ailleurs un jeu fixe de wrappers avant de comparer : timeout, time, nice, nohup, stdbuf, les commandes internes command et builtin, et le noglob de zsh. Une règle Bash(npm test:*) couvre donc aussi timeout 30 npm test, et un deny ne se contourne pas en préfixant la commande.

Sur les liens symboliques, le traitement est volontairement asymétrique : une règle allow ne s'applique que si le lien et sa cible correspondent tous les deux, alors qu'une règle deny s'applique dès que l'un des deux correspond.

  1. Placez-vous dans le projet

    Fenêtre de terminal
    cd ~/Projets/lab-claude
  2. Créez .claude/settings.json partagé

    {
    "$schema": "https://json.schemastore.org/claude-code-settings.json",
    "permissions": {
    "allow": [
    "Bash(uv run ruff check:*)",
    "Bash(uv run ruff format:*)",
    "Bash(uv run pytest:*)",
    "Bash(uv add:*)",
    "Bash(uv sync:*)",
    "Bash(git status)",
    "Bash(git diff:*)",
    "Bash(git log:*)"
    ],
    "deny": [
    "Bash(rm -rf:*)",
    "Bash(curl:*)",
    "Bash(wget:*)",
    "Read(./.env)",
    "Read(./.env.*)",
    "Read(./secrets/**)"
    ],
    "ask": [
    "Bash(git push:*)",
    "Bash(git commit:*)"
    ]
    },
    "env": {
    "PYTHONDONTWRITEBYTECODE": "1"
    },
    "includeCoAuthoredBy": false,
    "cleanupPeriodDays": 30
    }
  3. Validez la syntaxe JSON

    Fenêtre de terminal
    python3 -c "import json; json.load(open('.claude/settings.json'))" && echo "JSON valide"
  4. Démarrez une session et vérifiez

    Fenêtre de terminal
    claude

    Puis dans la session :

    /status
  5. Confirmez que .claude/settings.local.json est bien ignoré

    Fenêtre de terminal
    git check-ignore .claude/settings.local.json

Deux fichiers, deux usages distincts :

FichierContenu recommandé
.claude/settings.jsonRègles stables, pertinentes pour toute l'équipe (allow de uv run, deny des secrets)
.claude/settings.local.jsonPréférences personnelles temporaires (modèle préféré, allow spécifique à votre machine)

Règle simple : si un autre développeur rejoue votre configuration sans souci, c'est du settings.json. Si ça dépend de votre machine ou de votre compte, c'est du settings.local.json.

La commande /status affiche d'où vient chaque valeur (Managed, Local, Project, User). C'est le réflexe à avoir quand une permission semble bloquée ou accordée sans raison apparente.

Dans une session Claude :

/status

Vous y trouvez :

  • Les scopes chargés et leur chemin sur disque
  • Les règles allow, deny, ask résultantes après fusion
  • Le mode de permission actif

Pourquoi ces règles dans le settings.json partagé :

  • Bash(uv run ruff check:*) et Bash(uv run pytest:*) : validations répétées à chaque cycle, aucune raison de prompter
  • Bash(git status), Bash(git diff:*), Bash(git log:*) : lecture seule, sans risque
  • Bash(git push:*) et Bash(git commit:*) dans ask : action visible côté équipe, confirmation souhaitable
  • Read(./.env) et Read(./secrets/**) en deny : protection systématique des secrets
  • Bash(rm -rf:*), Bash(curl:*), Bash(wget:*) en deny : opérations destructives ou exfiltration réseau
  • includeCoAuthoredBy: false : l'attribution par défaut peut gêner dans certains workflows de revue

Ces symptômes se ressemblent tous, mais ils relèvent de trois familles distinctes. Soit la règle est au mauvais endroit, et /status le dit. Soit sa syntaxe ne correspond pas à ce que vous croyez, en général l'oubli du deux-points. Soit une règle plus large la rend sans effet, cas du nom d'outil seul vu plus haut. La colonne du milieu identifie laquelle des trois.

SymptômeCause probableCorrection
Claude redemande une autorisation pourtant dans allowMotif trop restrictif ou syntaxe incorrecteUtiliser la forme Bash(prefix:*) avec deux-points, pas Bash(prefix *)
deny non respectéRègle placée dans le mauvais scopeVérifier avec /status d'où vient la règle
Éditeur ne complète pas le JSON$schema absent ou incorrectAjouter la ligne $schema en tête du fichier
settings.local.json apparaît dans git statusProjet créé avant le fichierAjouter .claude/settings.local.json au .gitignore manuellement
Permissions perso poussées sur le dépôtRègle mise dans settings.json au lieu de .local.jsonDéplacer la règle vers le fichier local

Une configuration se vérifie sur le résultat effectif, pas sur ce que le fichier semble dire. Les deux derniers points de cette liste sont ceux qu'on oublie le plus souvent, et ce sont précisément ceux qui font qu'une règle ne s'applique pas.

  • J'ai identifié les quatre fichiers de configuration et leur ordre de priorité
  • J'ai créé .claude/settings.json avec $schema en tête
  • J'ai au moins une entrée dans allow, deny et ask
  • J'ai validé la syntaxe JSON du fichier
  • J'ai vérifié la configuration active avec /status
  • Je sais où mettre une règle perso (settings.local.json) vs équipe (settings.json)
  • .claude/settings.local.json est bien ignoré par git
  • Aucune règle nue (Bash, Read, Edit) ne rend mes règles détaillées inutiles
  • Mes préfixes utilisent bien Bash(commande:*), avec le deux-points en fin de motif
  • Le settings.json transforme les garde-fous en contrat durable du projet
  • deny protège, allow accélère, ask garde la main sur les actions visibles
  • Les permissions fusionnent entre portées au lieu de s'écraser, contrairement aux autres réglages
  • Un nom d'outil seul ouvre tout l'outil et annule les règles détaillées qui le concernent
  • La forme :* n'est reconnue qu'en fin de motif, et les wrappers comme timeout sont retirés avant comparaison
  • settings.json se partage, settings.local.json reste local
  • /status est le seul juge de ce qui est vraiment actif

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn