Mein Nachtlauf arbeitet Issues ab, während ich schlafe. Niemand sitzt daneben und klickt Berechtigungen weg. Also muss vorher feststehen, was Claude Code tun darf und was nicht. Bei mir steht das in der settings.json, und sie gilt auch dann, wenn niemand hinsieht.
Die Datei kennt drei Listen: deny, ask, allow. Interessant ist die Reihenfolge.
Erst deny, dann ask, dann allow
Jeder Werkzeugaufruf läuft durch diese drei Listen, und der erste Treffer entscheidet. Wie genau eine Regel formuliert ist, spielt dabei keine Rolle. Ein breites Bash(aws *) in deny blockiert auch das harmlose Bash(aws s3 ls), selbst wenn genau das in allow steht. Eine deny-Regel kann keine Ausnahmen tragen.
Über die Ebenen hinweg gilt dasselbe. Es gibt vier: ~/.claude/settings.json für mich persönlich, .claude/settings.json im Projekt für das Team, .claude/settings.local.json für meine eigenen Sonderfälle, dazu die zentral ausgerollten Managed Settings. Die drei Listen werden über alle Ebenen zusammengeworfen statt überschrieben. Ein deny in meinen Benutzereinstellungen überlebt jedes Projekt, das sich das Gegenteil wünscht.
Verbieten
{
"permissions": {
"deny": [
"Bash(rm:*)",
"Bash(sudo:*)",
"Bash(curl:*)",
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
Zwei Details, die man kennen sollte. Ein Read-Verbot sperrt denselben Pfad auch für Edit und Write, eine Datei in secrets/ wird also weder gelesen noch überschrieben. Und ein nackter Werkzeugname ohne Klammern, also "Bash" statt "Bash(rm:*)", nimmt das Werkzeug komplett aus dem Kontext. Das Modell sieht es dann gar nicht mehr.
Erlauben ist der wichtigere Teil
Die meisten schreiben eine deny-Liste und hören dann auf, weil Verbieten sich nach Sicherheit anfühlt. Danach klicken sie zwanzigmal am Tag „Approve“ für npm test und greifen irgendwann entnervt zu --dangerously-skip-permissions. Genau dort entsteht der Schaden, den die deny-Liste verhindern sollte.
{
"permissions": {
"allow": [
"Bash(npm run test:*)",
"Bash(npm run lint)",
"Bash(git status)",
"Bash(git diff:*)",
"Edit(/src/**)",
"Edit(/tests/**)"
],
"ask": [
"Bash(git push:*)",
"Bash(docker:*)"
]
}
}
ask ist die Liste, die ich am meisten benutze. Sie erzwingt eine Rückfrage, und zwar auch im Auto-Modus, wo sonst ein Klassifizierer an meiner Stelle entscheidet. Push und Merge bleiben so menschliche Kontrollpunkte, ohne dass ich den Rest der Arbeit anfassen muss.
Wo die Regeln aufhören
Muster, die Argumente einschränken sollen, sind brüchig. Bash(curl https://github.com/ *) sieht nach Kontrolle aus und greift nicht mehr, sobald ein -X GET davorsteht, die URL aus einer Variablen kommt oder ein Redirect woanders hinführt. Wenn es auf das Netzwerk ankommt, sperre ich curl und wget ganz und gebe stattdessen einzelne Domains über WebFetch(domain:...) frei.
Read- und Edit-Verbote gelten für Claudes eigene Dateiwerkzeuge und für Befehle, die Claude Code als Dateizugriff erkennt, etwa cat oder sed. Ein Python-Skript, das die Datei selbst öffnet, ist davon nicht erfasst. Für diese Ebene gibt es die Sandbox, die auf Betriebssystemebene arbeitet.
Der wichtigste Satz steht in der Doku fast beiläufig: Berechtigungsregeln setzt Claude Code durch, nicht das Modell. Was in der CLAUDE.md steht, ist eine Bitte. Eine deny-Regel ist ein Gate. Leitplanken müssen scheitern können, nicht argumentieren.
Wie ich anfangen würde
Nimm die fünf Befehle, bei denen du letzte Woche am häufigsten „Approve“ geklickt hast, und schreib sie mit :* in die allow-Liste. Nimm rm, sudo und deine .env-Dateien und schreib sie in die deny-Liste. Push und Deploy kommen nach ask. Danach /permissions in der laufenden Session aufrufen: Der Dialog zeigt jede aktive Regel und die Datei, aus der sie stammt.
Nach einer Woche liegt die Reibung dort, wo sie hingehört, bei den drei oder vier Entscheidungen, die tatsächlich meine sind. Gute Leitplanken machen nicht langsamer. Sie sind die Voraussetzung für Tempo.
Warum ich meine eigene Liste hier nicht anhänge, steht im nächsten Post.

Schreibe einen Kommentar