Claude Code permissions are three lists of rules and one mode, read from five files in a fixed order, and a developer can turn the whole thing off in three different ways. That is the model in one sentence, and it is written for the developer who owns one laptop. This article reads it from the other side: the security team that has to decide what the standard is for two hundred laptops, hand it to the platform team, and know it held. It covers the rules and their exact syntax, the six modes and what each one runs without asking, the precedence of the files, the limits Anthropic's own documentation states, and a baseline to deploy. The wider picture, what Claude Code installs, reads and loads, is in the Claude Code security piece; this one stays on permissions.
What a permission rule is
A rule names a tool, and optionally what the tool is doing, in the form Tool or Tool(specifier). Three examples from the documentation cover most of what you will meet: Bash(npm run *) matches every npm run command, Read(./.env) matches reading the .env file in the current directory, and WebFetch(domain:example.com) matches fetches to one host (source: Anthropic, configure permissions).
Rules go in one of three lists under permissions in a settings file:
allow: the call runs without a prompt.ask: the call always prompts, even if an allow rule also matches.deny: the call is refused. A bare tool name,BashorWebFetch, removes the tool from the agent entirely; a scoped rule such asBash(rm *)leaves the tool and blocks the matching calls.
The documentation is exact about the order: deny, then ask, then allow, first match wins, and a broad deny beats a narrow allow. Bash(aws *) in deny blocks aws s3 ls even when Bash(aws s3 ls) sits in allow. For a security team that is the useful property: a deny is a deny, whatever a developer allows underneath it.
One more line worth quoting, because it settles an argument that comes up in every review: permission rules are enforced by Claude Code, not by the model. Nothing written in a prompt or a CLAUDE.md file changes what Claude Code allows.
The six modes, and what each one runs without asking
A permission mode sets what a session does without asking anyone. The documentation lists six (source: Anthropic, permission modes):
| Mode | Runs without asking |
|---|---|
default (labelled Manual) |
Reads only |
acceptEdits |
Reads, file edits, and common filesystem commands such as mkdir, touch, mv, cp |
plan |
Reads, plus classifier-approved commands where auto mode is available |
auto |
Everything, with a second model reviewing each action instead of the person |
dontAsk |
Reads and pre-approved tools; anything that would prompt is denied |
bypassPermissions |
Everything |
Two facts about the defaults matter for a standard. From Claude Code v2.1.283, auto mode is the built-in starting mode for terminal and VS Code sessions, so a developer who never touched a setting is already running with a classifier in the loop rather than themselves. And auto and bypassPermissions do not take effect from a project's .claude/settings.json or .claude/settings.local.json; only user or managed settings, or a flag, can start a session in those two. A repository cannot quietly put its cloners into bypass mode (source: Anthropic, settings).
bypassPermissions deserves its own paragraph. It skips prompts including writes to the protected paths such as .git and .claude, and the documentation's guidance is a container or VM without internet access. Claude Code refuses to start in it as root or under sudo, and the first interactive session with it enabled shows a dialog the developer has to accept, once, after which the acceptance is stored in user settings. A short list of actions is never auto-approved in any mode, bypass included: tools matched by an explicit ask rule, tools that need a person to answer, and rm or rmdir aimed at a critical path such as the filesystem root, the home directory or the working directory.
The five files, and why only one of them is yours
Permissions follow the same precedence as every other setting, highest first (source: Anthropic, settings):
- Managed settings: a
managed-settings.jsonfile, an MDM policy, or server-managed settings from the claude.ai console. Nothing a developer sets overrides them. - Command line:
--settingsfor one session. .claude/settings.local.json: the developer's private file for one project, kept out of git..claude/settings.json: the project file the team commits.~/.claude/settings.json: the developer's file for every project on the machine.
The first is the only one the security team owns. The other four belong to the developer or the repository, and any of them can add allow rules. Deny is the exception that makes the standard work: a deny at any level blocks an allow at any other level, and a managed deny cannot be overridden by --allowedTools or anything else. If you want managed settings to be the only source of rules at all, allowManagedPermissionRulesOnly ignores every developer and project list (source: Anthropic, configure permissions). Where the managed file lives on each platform, and how MDM delivers it, is in the Claude Code security piece.
The three ways the prompt goes away
On a real machine the prompt is not switched off by policy. It goes away in one of three ways, and only the third is hard to see.
A mode. A developer presses Shift+Tab and cycles into acceptEdits or, where enabled, bypassPermissions; or sets permissions.defaultMode in their user settings so every session starts that way. The status bar shows it, which is the only good thing about it.
A flag. claude --dangerously-skip-permissions starts the session in bypassPermissions. It lives in shell history and in scripts, and a scheduled job that runs Claude Code this way is an agent with nobody at the keyboard and no prompts.
Rules, one at a time. Every Bash prompt offers "Yes, and don't ask again". Each yes saves an allow rule to .claude/settings.local.json at the repository root, per command, permanently. The documentation says it plainly: Claude Code writes that file too. Six months of a developer answering yes produces a file that allows git push, curl, ssh, docker and whatever else came up, and the session still shows Manual mode in the status bar. This is the switch that removes the person from the loop without anyone deciding to.
Every one of the three leaves a line in a file on the laptop. Join the launch list and see what those files say on your own machines, from the first mail
What a Read deny actually covers
A Read deny rule for a path blocks more than the Read tool, and the documentation lists exactly what: Claude's own file tools, the file commands Claude Code recognises in Bash, cat, head, tail, sed and tee, the targets of shell redirections such as > file and < file, and, from v2.1.208, the Edit and Write tools on the same path, including creating a new file there. It also states what the rule does not cover: a command that reads a file without naming it, such as grep -r pattern . run from the directory that holds the file, and any subprocess that opens files on its own, such as a Python or Node script. For enforcement that does not depend on the command text, the documentation points at the sandbox (source: Anthropic, configure permissions).
The path forms decide whether a rule matches what you meant:
//pathis an absolute path from the filesystem root.~/pathis from the home directory./pathanchors at the settings source: in project settings that is the project directory, but in~/.claude/settings.jsonit is~/.claude/path, soRead(/secrets/**)in user settings blocks~/.claude/secrets/**and not a secrets folder in any project.pathor./pathis relative to the current directory, and a bare filename such as.envmatches at any depth beneath it.
For an organisation-wide rule the documentation's answer is the // or ~/ form: Read(//**/.env) blocks every .env on the filesystem, Read(~/.aws/**) the cloud profiles.
The limits the documentation states
A Bash rule matches the command text as the agent writes it, after wrappers such as timeout and nice are stripped and compound commands are split. It does not match the same program invoked another way. The documentation's own table: Bash(curl *) in deny stops curl https://example.com and does not stop /usr/bin/curl https://example.com or sh -c 'curl https://example.com'; Bash(git push *) stops git push origin main and not git -C . push origin main. Its words: a deny or ask rule covers the invocation Claude usually produces and isn't a security boundary around the program (source: Anthropic, configure permissions).
So the standard has to be honest about what a rule is. A deny on curl and wget, paired with WebFetch(domain:...) allow rules for the hosts a team needs, is the pattern the documentation itself recommends for keeping network access to known hosts. It stops the form the agent produces on its own. It does not stop a determined instruction that spells the path out, and the documentation says to pair it with the sandbox network allowlist where the restriction must hold, or with a PreToolUse hook that inspects the full command before it runs.
A baseline for a company
This is a managed-settings block a platform team can deploy today. Each line is one decision from the sections above, and none of it is exotic: every key is in the documentation linked here.
{
"permissions": {
"disableBypassPermissionsMode": "disable",
"deny": [
"Read(//**/.env)",
"Read(~/.aws/**)",
"Read(~/.ssh/**)",
"Read(~/.config/gcloud/**)",
"Bash(curl *)",
"Bash(wget *)"
],
"ask": [
"Bash(git push *)",
"Bash(sudo *)"
]
}
}
disableBypassPermissionsMode: removes the mode that skips every check, everywhere, and a developer cannot re-enable it from any other file.- The four
Readdenies: the places an estate keeps secrets, in the absolute and home forms so they apply inside every project, and they covercat, redirections and edits too. curlandwgetin deny: the documentation's own recommendation, paired withWebFetch(domain:...)allow rules the team adds for the hosts it needs.git pushandsudoin ask: an ask rule cannot be silenced by an allow rule or by a mode, so these two prompt a person in every session, even auto.
What the baseline does not do: it does not tell you which developer has permissions.defaultMode set to acceptEdits, which local file holds a Bash(ssh *) allow from March, or which machines never received the managed file. It sets the floor. Knowing the floor is there is the next section.
A worked example: one developer's local file, read line by line
A .claude/settings.local.json from a working repository, the kind the "don't ask again" button writes:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git commit *)",
"Bash(git push:*)",
"Bash(curl -s https://api.internal.example/health)",
"WebFetch(domain:docs.example.com)",
"Read(//Users/dev/.aws/credentials)"
]
}
}
Line by line. npm run and git commit are what the developer meant to allow, and are harmless. git push:* is the :* spelling of a trailing wildcard, so it allows every push, including a force push, without a prompt; an ask rule for git push in managed settings would still prompt, because ask beats allow. The curl line looks narrow, and it is: an exact command matches only itself. But under the managed baseline it never runs at all, because the managed Bash(curl *) deny beats it. The WebFetch line is fine. The last line is the one that matters: a Read allow on the cloud credentials file, saved the day the agent asked to read it and the developer said yes. It grants nothing under the baseline, because the managed Read(~/.aws/**) deny wins, and it is exactly the line a security team wants to know exists, because it says what the agent tried and what the developer let through.
Why one laptop is not a standard
Every control in this article is a line in a file, and a person with an afternoon can read the files on one laptop and set them right. That is a repair. The standard is the same lines on every laptop, still true next week. Nobody can open two hundred copies of settings.local.json on a Tuesday to see what was allowed on Monday, or know which developer set defaultMode to acceptEdits in their user file, or which repository committed an allow list into .claude/settings.json that a hundred people have since cloned. The files change daily, by hand and by the agent's own prompts. A standard needs the files read on every machine, every day, with the exceptions listed and dated. That is the difference between a policy and a record, and an auditor asks for the record. The governance piece covers who owns which decision; the lockdown guide is the version of this baseline written for the person who signs it off.
How ZYBE handles this
ZYBE reads the same five files on every machine it is installed on, the moment it is installed, and reports every allow rule per machine with the file it came from and the fix beside it. The rules behind this article are its rules: Agent acts without asking where a mode or a flag removes the prompt, Agent may run shell commands without asking where a shell rule stands, Standing permission granted for each allow rule a developer accepted, Agent safety control turned off where a shipped control was disabled, and Credentials an agent can read for the secrets in reach of an agent whatever the rules say. In block mode the standard holds even where a rule allows: a credential file read or a remote script piped into a shell is stopped on the call, before it runs, and the agent is told why. The console shows the estate by machine and by developer, and the compliance view files the permission evidence under EU AI Act Article 14 and OWASP LLM06 without a second inventory.
Join the launch list: first access when ZYBE opens, and the lockdown guide in the first mail
Frequently asked questions
How do I stop Claude Code from asking permission? Three ways, and each one is a decision the security team should know about. A permission mode: auto has a classifier approve calls instead of a person, and bypassPermissions skips the prompts entirely. A flag: --dangerously-skip-permissions starts a session in bypassPermissions, and Claude Code refuses it under sudo or root. Or rules: every "Yes, and don't ask again" on a Bash prompt saves an allow rule to .claude/settings.local.json in the repository, and those rules pile up. The first two show up in a session's status bar; the third is invisible until someone reads the file.
What does --dangerously-skip-permissions do? It starts Claude Code in bypassPermissions mode, where tool calls run without permission prompts, including writes to protected paths such as .git and .claude. The first interactive session with it shows a warning dialog that you have to accept, and the acceptance is saved to user settings so it never asks again. Anthropic's own guidance is to use it only in a container or VM without internet access. An organisation can make the mode unavailable everywhere with permissions.disableBypassPermissionsMode set to "disable" in managed settings.
Where are Claude Code permissions stored? In five places, and the order matters. Managed settings, which an organisation deploys and a developer cannot override; the --settings flag for one session; .claude/settings.local.json, the developer's private file for one project, kept out of git; .claude/settings.json, the project file the team commits; and ~/.claude/settings.json, the developer's file for every project. Rules a developer accepts at a prompt land in the local file, at the repository root. The /permissions dialog inside a session lists every rule and the file it came from.
Can Claude Code permissions be enforced for a team? Yes, through managed settings: a managed-settings.json file placed by MDM, an MDM policy, or server-managed settings from the claude.ai console. A deny rule there cannot be overridden by any user, project, local or command-line setting. With allowManagedPermissionRulesOnly, managed settings become the only source of permission rules at all, and every developer's own allow list is ignored. What managed settings cannot do is tell you which machines have them, or what a developer allowed before they arrived; that is the inventory question, and it needs something reading the files on every machine.