Files
claude/settings.json
T
Bastien Chanot 3b0167c6cb feat(settings): rebuild destructive-command cover in autoMode, scope the classifier environment
`permissions.ask` gates nothing under `defaultMode: auto` (LRN-146,
verified live), so the ten rules that left the static tiers had no cover
left: rsync / kill -9 / killall / pkill out of deny, and python3 -c /
python -c / xargs / sed / cp / mv out of ask.

autoMode.soft_deny (7 rules) takes over what an explicit instruction
should be able to clear: writes outside the working directory,
rsync --delete, SIGKILL and kill-by-name, in-place edits spanning more
than one file, directory moves, and inline interpreters or xargs that
delete or write outside the cwd. Intent clears a soft block for the
current turn only, stated as a rule since no setting expresses it.

autoMode.hard_deny (3 rules) takes the classes no command pattern can
express: secret exfiltration, production deployment, and disarming the
guardrails. Adding a restriction stays allowed, removing one does not.

permissions.deny gains ten .env reader rules (sed awk cut tr sort uniq
diff od xxd strings). Six of those tools sat in permissions.allow, so
reading a .env through them triggered nothing.

autoMode.environment named another project, its FTP deploy target and its
customer data, inside the file link.sh:21 symlinks to
~/.claude/settings.json, where it reached every repo and contradicted
this one's Gitea remote. Rewritten machine-generic; the project facts
moved to that project's gitignored .claude/settings.local.json. All three
lists now open with "$defaults", which the original omitted, so the
built-in classifier entries are inherited rather than replaced.

doctor.sh check_automode backstops both defects. SETTINGS.md documents
the block and a tier-choice table. README no longer claims the ask tier
makes every mcp__magic__* call require a live confirmation.
2026-09-15 19:44:24 +02:00

398 lines
15 KiB
JSON

{
"cleanupPeriodDays": 7,
"attribution": {
"commit": "",
"pr": "",
"sessionUrl": false
},
"permissions": {
"allow": [
"Bash(git status)",
"Bash(git log*)",
"Bash(git diff*)",
"Bash(git branch*)",
"Bash(git fetch*)",
"Bash(git pull*)",
"Bash(git add *)",
"Bash(git commit*)",
"Bash(git checkout *)",
"Bash(git switch *)",
"Bash(git stash)",
"Bash(git stash push*)",
"Bash(git stash list*)",
"Bash(git stash show*)",
"Bash(git tag*)",
"Bash(git show*)",
"Bash(ls *)",
"Bash(ls)",
"Bash(find *)",
"Bash(cat *)",
"Bash(head *)",
"Bash(tail *)",
"Bash(grep *)",
"Bash(rg *)",
"Bash(fd *)",
"Bash(wc *)",
"Bash(echo *)",
"Bash(pwd)",
"Bash(which *)",
"Bash(type *)",
"Bash(whoami)",
"Bash(uname *)",
"Bash(mkdir -p *)",
"Bash(touch *)",
"Bash(jq *)",
"Bash(yq *)",
"Bash(awk *)",
"Bash(sort *)",
"Bash(uniq *)",
"Bash(tr *)",
"Bash(cut *)",
"Bash(diff *)",
"Bash(rtk grep *)",
"Bash(*/rtk grep *)",
"Bash(rtk ls)",
"Bash(rtk ls *)",
"Bash(*/rtk ls)",
"Bash(*/rtk ls *)",
"Bash(rtk cat *)",
"Bash(*/rtk cat *)",
"Bash(rtk head *)",
"Bash(*/rtk head *)",
"Bash(rtk tail *)",
"Bash(*/rtk tail *)",
"Bash(rtk wc *)",
"Bash(*/rtk wc *)",
"Bash(rtk diff *)",
"Bash(*/rtk diff *)",
"Bash(rtk git status)",
"Bash(*/rtk git status)",
"Bash(rtk git log*)",
"Bash(*/rtk git log*)",
"Bash(rtk git diff*)",
"Bash(*/rtk git diff*)",
"Bash(rtk git show*)",
"Bash(*/rtk git show*)",
"Bash(rtk git branch*)",
"Bash(*/rtk git branch*)",
"Read(**/*.md)",
"Read(**/*.txt)",
"Read(**/*.json)",
"Read(**/*.yaml)",
"Read(**/*.yml)",
"Read(**/*.toml)",
"Read(**/*.lock)",
"Read(**/*.gitignore)",
"Read(**/*.dockerignore)",
"Read(**/.claudeignore)",
"Read(**/Makefile)",
"Read(**/Dockerfile*)",
"Read(**/docker-compose*)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(rm -rf /*)",
"Bash(rm -r *)",
"Bash(rm -fr *)",
"Bash(rmdir *)",
"Bash(git push --force)",
"Bash(git push --force *)",
"Bash(git push -f*)",
"Bash(git push * +*)",
"Bash(git reset --hard*)",
"Bash(git clean -fd*)",
"Bash(sudo rm*)",
"Bash(sudo chmod*)",
"Bash(sudo chown*)",
"Bash(sudo dd*)",
"Bash(su *)",
"Bash(chmod 777 *)",
"Bash(chmod -R 777 *)",
"Bash(ssh *)",
"Bash(scp *)",
"Bash(nc *)",
"Bash(netcat *)",
"Bash(crontab *)",
"Bash(systemctl *)",
"Bash(service *)",
"Bash(npm install -g *)",
"Read(**/.env)",
"Read(**/.env.*)",
"Read(**/secrets/**)",
"Read(**/*.pem)",
"Read(**/*.key)",
"Read(**/*.p12)",
"Read(**/*.pfx)",
"Read(**/id_rsa*)",
"Read(**/id_ed25519*)",
"Read(**/.ssh/**)",
"Read(**/credentials)",
"Read(**/credentials.json)",
"Read(**/.aws/credentials)",
"Read(**/.azure/**)",
"Edit(**/.env)",
"Edit(**/.env.*)",
"Edit(**/secrets/**)",
"Edit(**/*.pem)",
"Edit(**/*.key)",
"Edit(**/*.p12)",
"Edit(**/*.pfx)",
"Edit(**/id_rsa*)",
"Edit(**/id_ed25519*)",
"Edit(**/.ssh/**)",
"Edit(**/credentials)",
"Edit(**/credentials.json)",
"Edit(**/.aws/credentials)",
"Edit(**/.azure/**)",
"Edit(**/*.lock)",
"Edit(**/package-lock.json)",
"Edit(**/pnpm-lock.yaml)",
"Edit(**/go.sum)",
"Edit(**/node_modules/**)",
"Bash(eval *)",
"Bash(exec *)",
"Bash(find * -delete*)",
"Bash(find * -exec rm*)",
"Bash(find * -execdir rm*)",
"Bash(find * -exec *)",
"Bash(find * -execdir *)",
"Bash(perl -e *)",
"Bash(ruby -e *)",
"Bash(cat .env)",
"Bash(cat .env.*)",
"Bash(cat */.env)",
"Bash(cat */.env.*)",
"Bash(cat */secrets/*)",
"Bash(cat */*.pem)",
"Bash(cat */*.key)",
"Bash(cat */id_rsa*)",
"Bash(cat */id_ed25519*)",
"Bash(cat */.aws/credentials)",
"Bash(head .env)",
"Bash(head .env.*)",
"Bash(tail .env)",
"Bash(tail .env.*)",
"Bash(less .env)",
"Bash(less .env.*)",
"Bash(more .env)",
"Bash(more .env.*)",
"Bash(grep * .env)",
"Bash(grep * .env.*)",
"Bash(sed * .env*)",
"Bash(awk * .env*)",
"Bash(cut * .env*)",
"Bash(tr * .env*)",
"Bash(sort * .env*)",
"Bash(uniq * .env*)",
"Bash(diff * .env*)",
"Bash(od * .env*)",
"Bash(xxd * .env*)",
"Bash(strings * .env*)",
"Bash(env)",
"Bash(printenv)",
"Bash(printenv *)",
"Bash(export *)",
"Bash(cp .env*)",
"Bash(cp **/.env*)",
"Bash(cp **/secrets/*)",
"Bash(mv .env*)",
"Bash(mv **/.env*)",
"Bash(mv **/secrets/*)",
"Bash(git add .env*)",
"Bash(git add **/.env*)",
"Bash(cp **/id_rsa*)",
"Bash(cp **/id_ed25519*)",
"Bash(cp **/.ssh/*)",
"Bash(source /dev/stdin)",
"Bash(xargs * .env*)",
"Bash(tar * .env*)",
"Bash(zip * .env*)",
"Bash(base64 .env*)",
"Bash(rtk cat *.env*)",
"Bash(*/rtk cat *.env*)",
"Bash(rtk grep * .env*)",
"Bash(*/rtk grep * .env*)",
"Bash(rtk head *.env*)",
"Bash(*/rtk head *.env*)",
"Bash(rtk tail *.env*)",
"Bash(*/rtk tail *.env*)"
],
"ask": [
"Bash(bash -c *)",
"Bash(curl * | bash)",
"Bash(wget * | bash)",
"Bash(curl * | sh)",
"Bash(wget * | sh)",
"Bash(mkfifo *)",
"Bash(node -e *)",
"Bash(git push *)",
"Bash(git push)",
"Bash(docker run *)",
"Bash(docker exec *)",
"Bash(docker-compose up*)",
"Bash(docker compose up*)",
"Bash(brew install *)",
"Bash(apt install *)",
"Bash(apt-get install *)",
"Bash(dnf install *)",
"Bash(pacman -S *)",
"WebSearch",
"WebFetch",
"Bash(git stash pop*)",
"Bash(git stash drop*)",
"Bash(git stash clear)",
"mcp__magic__21st_magic_component_builder",
"mcp__magic__21st_magic_component_refiner",
"mcp__magic__21st_magic_component_inspiration",
"mcp__magic__logo_search"
],
"defaultMode": "auto",
"disableBypassPermissionsMode": "disable",
"additionalDirectories": []
},
"model": "opus[1m]",
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/session-start.sh"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/rtk-rewrite.sh"
}
]
}
],
"Notification": [
{
"matcher": "permission_prompt|idle_prompt|agent_needs_input|elicitation_dialog|elicitation_url_dialog",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/notify-attention.sh",
"timeout": 5,
"statusMessage": "Ringing terminal bell..."
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/notify-attention.sh",
"timeout": 5,
"statusMessage": "Ringing terminal bell..."
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/design-toolchain-reminder.sh",
"timeout": 5,
"statusMessage": "Checking design signals..."
},
{
"type": "command",
"command": "bash ~/.claude/hooks/ctx7-reminder.sh",
"timeout": 5,
"statusMessage": "Checking fast-libs..."
}
]
}
]
},
"statusLine": {
"type": "command",
"command": "bash ~/.claude/hooks/statusline.sh"
},
"enabledPlugins": {
"example-skills@anthropic-agent-skills": false,
"ui-ux-pro-max@ui-ux-pro-max-skill": true,
"security-guidance@claude-code-plugins": true,
"superpowers@superpowers-marketplace": true,
"pr-review-toolkit@claude-code-plugins": false,
"frontend-design@claude-plugins-official": true
},
"extraKnownMarketplaces": {
"claude-code-plugins": {
"source": {
"source": "github",
"repo": "anthropics/claude-code"
}
},
"superpowers-marketplace": {
"source": {
"source": "github",
"repo": "obra/superpowers-marketplace"
}
},
"ui-ux-pro-max-skill": {
"source": {
"source": "github",
"repo": "nextlevelbuilder/ui-ux-pro-max-skill"
}
},
"anthropic-agent-skills": {
"source": {
"source": "github",
"repo": "anthropics/skills"
}
}
},
"effortLevel": "xhigh",
"remoteControlAtStartup": true,
"inputNeededNotifEnabled": true,
"skipAutoPermissionPrompt": true,
"autoMode": {
"soft_deny": [
"$defaults",
"Scope of intent: an instruction clears a SOFT BLOCK for the current turn only. An approval given in an earlier turn is not an approval now, and the same action repeated in a later turn has to be asked for again.",
"Writing outside the current working directory: `cp`, `mv`, `sed -i`, `rsync`, `tee`, or a shell redirection whose destination resolves outside the cwd. Several sibling projects live under `~/Documents/`, so the realistic failure is writing into the wrong one, where git recovers nothing. Clear only when the user named the destination in this turn.",
"`rsync` invoked with `--delete`. It removes files at the destination that are absent from the source, with no undo. Clear only against a destination the user named in this turn.",
"Sending SIGKILL (`kill -9`) or killing processes by name (`killall`, `pkill`). These reach processes outside this session, including the user's editors, shells, dtach sessions and background jobs, and the target is chosen by a pattern, so a typo kills the wrong thing. Clear only when the user named the process in this turn.",
"Editing more than one file in place in a single command: `sed -i` or `perl -pi` over a glob, or a loop over `git ls-files`. The damage is not loss, since git recovers it, but a diff spanning hundreds of files that nobody reads before committing. `sed -i` on a single named file passes. Clear only when the user asked for the sweep.",
"Moving or renaming a directory inside the repo (`mv src/api src/api_old`, or any `mv` of a tree). It breaks imports and paths silently, and the breakage surfaces far from the command. Clear only when the user asked for that move.",
"An inline interpreter or `xargs` that deletes, or that writes outside the current working directory: `python3 -c`, `python -c` or `node -e` calling `rmtree`, `remove`, `unlink` or `truncate`; `xargs` feeding `rm`, `mv` or `dd`. `find ... | xargs rm` is the case that matters, since it routes around the `find * -exec rm` deny rule. Reading, computing, and editing a file inside the working directory pass untouched."
],
"hard_deny": [
"$defaults",
"Secret exfiltration: reading a `.env`, an SSH key, a token or a credentials file, and then sending what it contained outside this machine. That covers an HTTP request body or header, a paste or gist service, a file upload, an email, and a commit or push to any repository. The static deny rules match the reading tools by pattern and cannot see the second half of the chain; read and send are separate steps and may sit several turns apart. No instruction clears this.",
"Production deployment: running a project's deploy script (`bin/deploy.sh` and its equivalents), any lftp, FTP, SFTP or rsync push to a hosting provider, and any action against a target whose name carries `prod` or `production` as a whole word or name segment. The user deploys by hand, out of session. A green test suite, a finished feature, or a plan step that reads \"deploy\" is not an instruction to deploy. No in-session instruction clears this.",
"Disarming the guardrails: removing or weakening an entry in the `deny`, `soft_deny` or `hard_deny` lists of any settings.json, passing `--no-verify` to git, deleting or chmod-ing `.githooks/pre-commit`, setting `disableAllHooks`, or switching `permissions.defaultMode` to `bypassPermissions`. Adding a restriction is fine; removing one is not. When a task is blocked by a guardrail, say so and stop, rather than widening the guardrail to get through. The user maintains these files by hand. No instruction clears this."
],
"environment": [
"$defaults",
"### Machine-specific (refines any \"None configured\" default above)",
"**Primary use of Claude Code**: software development on a personal Linux workstation. Single developer, no organization.",
"**Source control**: self-hosted Gitea at `git.bchanot.fr` (SSH on port 49220). Some checkouts under `/home/bchanot/Documents/` have no remote at all and are local-only.",
"**Repository visibility**: private. The Gitea instance is self-hosted and not publicly indexed, and a checkout with no remote is local-only. Treat every repo here as private unless its remote points at a public host.",
"**Trusted repo**: the checkout Claude Code is currently working in, under `/home/bchanot/Documents/`. No single repo is privileged over the others — read the current one, do not assume a previous session's project.",
"**Trusted internal domains**: `git.bchanot.fr` (self-hosted Gitea). It is the only internal service.",
"**Default / protected branches**: gitflow. `main` (prod) and `develop` (integration) are protected: a per-repo pre-commit hook refuses code commits on either (exempting `.claude/**` and merges) and Gitea enforces branch protection on both. Work lands on `feature/*`, `bugfix/*`, `chore/*`, `release/*`, `hotfix/*`.",
"**Secrets management**: `~/.claude/.env` is the single source of truth and lives outside every git tree; repos reach it through a gitignored symlink. Only `.env.example`, holding placeholders, is ever tracked. A real secret inside a repo is a defect, not a configuration.",
"**Internal sharing / snippet hosting**: none. Public paste, gist and pastebin services are outside the trust boundary.",
"**CI/CD deploy targets**: no CI system. Deploys run out of band from a per-project runbook, typically lftp/FTP to OVH mutualised hosting for web projects. Nothing deploys automatically on a push or a merge.",
"**Internal package registry**: none. Public npm and PyPI.",
"**Host containment**: an ordinary developer workstation with open internet and no sandbox. Nothing is contained by the environment itself.",
"**Sensitive remote targets**: any namespace, host, database or container whose name carries `prod` or `production` as a whole word or name segment.",
"**Sensitive data locations & audiences**: per-project `.env` files (gitignored) hold database, deploy and API credentials; some web projects store customer-submitted form data under a retention policy. Both are personal or client data — never send either to an external service."
]
}
}