What you will be able to do
- Write allow, ask and deny permission rules and predict exactly what a wildcard matches
- Choose a starting permission mode and lock out bypass mode and user-defined rules
- Restrict which MCP servers, plugins and hooks a team can use
- Pick an API provider and set model, version, spend and usage-visibility controls for a team
1.Permission rules: allow, ask and deny
Permissions decide which tools Claude Code may use and which files or domains it can reach. They cover Bash, Read, Edit, WebFetch, MCP and every other tool. There are three kinds of rule: allow rules let a tool run without manual approval, ask rules prompt for confirmation every time, and deny rules block the tool entirely.
A rule names a tool, and can add a specifier in parentheses. Bash on its own matches every shell command. Bash(npm run build) matches that exact command. Read(./.env) matches reads of that file. WebFetch(domain:example.com) matches fetches to that domain. MCP tools use their server prefix: mcp__puppeteer matches every tool on the puppeteer server, and mcp__puppeteer__puppeteer_navigate matches one tool. Subagents work the same way, so Agent(Explore) matches the Explore subagent.
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git commit *)"
],
"deny": [
"Bash(git push *)"
]
}
}Wildcards are where team rules tend to go wrong. A * stands for whatever text is in its position, including options. That makes Bash(git * main) match every git subcommand, including git -c, which can make git run a program you name. The space before a trailing * is part of the rule too.
| You write | Matches | Doesn't match |
|---|---|---|
| Bash(npm run build) | npm run build | npm run build --watch |
| Bash(npm run *) | npm run build, npm run test --watch, npm run | npm install |
| Bash(git * main) | git merge main, git push origin main, git -c core.fsmonitor=<script> diff main | git log |
| Bash(ls *) | ls -la, ls | lsof |
| Bash(ls*) | ls -la, lsof | (none listed) |
Checkpoint 1 of 6· Check yourself
A team wants to allow ls without a prompt but not other commands that start with the same letters. Which rule is too broad?
With no space before the *, the rule matches any command that starts with ls, including lsof. Bash(ls *) needs a space after ls.
“Bash(ls*) has no space, so it matches lsof too.”Source: code.claude.com
Checkpoint 2 of 6· Exam question
A security lead configures a project-level deny rule Bash(aws *) to block all AWS CLI usage from Claude Code. A developer later adds an allow rule Bash(aws s3 ls) to their local settings so they can list bucket contents. When the developer runs aws s3 ls in a session, what happens?
Correct answer: A — The command is blocked, because deny rules are evaluated before allow rules and a broad deny cannot carry allowlist exceptions from a narrower allow rule
- A. Correct. Rules are evaluated in the fixed order deny, then ask, then allow, and rule specificity does not change that order, so a broad deny rule like Bash(aws *) blocks every matching call even when a narrower allow rule also matches.
- B. Incorrect. Specificity does not override the deny-first evaluation order; a narrower allow rule matching the same call as a broader deny rule still loses to the deny rule.
- C. Incorrect. There is no rule-merging step that converts a deny/allow conflict into an ask prompt; the deny rule wins outright and the command is blocked with no prompt.
- D. Incorrect. Local settings can add stricter or looser rules within their own scope, but they do not grant an exception to a deny rule defined at a different scope, since deny rules from any scope are evaluated before allow rules.
Sources1
2.Permission modes and locking them down
Rules decide individual tool calls. The permission mode decides what happens to every call that no rule covers. Admins can choose the mode developers' terminal sessions start in with permissions.defaultMode, and can remove auto mode with permissions.disableAutoMode.
| Mode | Behaviour |
|---|---|
| default | Prompts for permission on first use of each tool (labeled Manual) |
| acceptEdits | Automatically accepts file edits and common filesystem commands such as mkdir, touch, mv, and cp |
| plan | Reads files and runs read-only shell commands but doesn't edit source files |
| auto | Runs without routine prompts; a background classifier checks actions first |
| dontAsk | Auto-denies every call that would otherwise prompt; pre-approved tools still run |
| bypassPermissions | Skips permission prompts, except for the actions no mode auto-approves |
Two managed keys stop developers from loosening policy. permissions.disableBypassPermissionsMode disables --dangerously-skip-permissions. allowManagedPermissionRulesOnly makes managed settings the only place permission rules can come from, so allow rules in user or project files stop counting.
Permission rules and sandboxing do different jobs. Permissions apply to every tool. Sandboxing (sandbox.enabled, sandbox.network.allowedDomains) adds OS-level filesystem and network isolation, but only for Bash, PowerShell and Monitor commands and their child processes. Finally, a managed policy CLAUDE.md delivers organization-wide instructions that load in every session.
Checkpoint 3 of 6· Fill the gap
This managed-settings.json should stop developers from launching with --dangerously-skip-permissions. Which key fills the blank?
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./secrets/**)"
],
" ? ": "disable"
},
"allowManagedPermissionRulesOnly": true
}permissions.disableBypassPermissionsMode is the key that disables --dangerously-skip-permissions. disableAutoMode removes auto mode, and defaultMode only sets the starting mode.
Source: code.claude.com3.Controlling MCP servers, plugins and hooks
For MCP servers, an organization can restrict which servers users add or connect to, deploy a fixed set, or provide remote servers to every user alongside their own. The keys are allowedMcpServers, deniedMcpServers, allowManagedMcpServersOnly and managedMcpServers, or a managed-mcp.json file deployed next to the managed settings file. Entries match a server by serverUrl (wildcards allowed), by serverCommand (the exact stdio command), or by serverName, the label the user gave it. In an allowlist, serverName is exact match only. In deniedMcpServers it can be a connector's display name, which is how you block a claude.ai connector. When a managed server is added, Claude Code connects it as the new settings arrive, with no restart.
| Setting | Unset (default) | Empty array [] | Populated |
|---|---|---|---|
| allowedMcpServers | All servers allowed | No servers allowed, apart from those that skip the allowlist check | Only matching servers allowed |
| deniedMcpServers | No servers blocked | No servers blocked | Matching servers blocked |
Repositories can also bring their own servers in .mcp.json. If a developer has trusted only a parent folder, Claude Code asks before connecting those servers, and approvals written in the repository's own settings don't count. A disabledMcpjsonServers entry rejects a named .mcp.json server in every session type.
Other extension points have matching controls. strictKnownMarketplaces, blockedMarketplaces and disableSideloadFlags limit where plugins come from. strictPluginOnlyCustomization means skills, agents, hooks and MCP servers can come only from plugins or managed settings. allowManagedHooksOnly and allowedHttpHookUrls restrict hooks.
Checkpoint 4 of 6· Match them up
Match each MCP list setting to its effect
Tap a term, then the definition that fits it.
An empty allowlist blocks everything apart from servers that skip the allowlist check. An empty denylist blocks nothing.
“No servers allowed, apart from those that skip the allowlist check”Source: code.claude.com
Checkpoint 5 of 6· Exam question
An organization's Owner wants to guarantee that only permission rules defined centrally by the security team take effect for every developer, so that even a well-intentioned engineer cannot loosen a restriction by editing their own settings. Which managed setting accomplishes this?
Correct answer: B — Set allowManagedPermissionRulesOnly to true, so user and project settings can no longer define any allow, ask, or deny rules
- A. Incorrect. This key locks down which MCP servers can be added or connected to; it has no effect on general permission rules like Bash or file access allow, ask, and deny entries.
- B. Correct. allowManagedPermissionRulesOnly is a managed-only setting that, when true, prevents user and project settings from defining any allow, ask, or deny permission rules at all, so only the rules the security team places in managed settings apply.
- C. Incorrect. This setting only blocks the bypassPermissions mode from being enabled; it does not stop developers from adding their own allow or ask rules in other permission modes.
- D. Incorrect. This setting restricts which plugin marketplace sources can be added, which is unrelated to permission rules for tools like Bash, Read, or Edit.
4.Provider, models, versions, spend and usage visibility
The first admin decision is the API provider, because it sets both authentication and billing. Claude for Teams / Enterprise is the default recommendation: Claude Code and claude.ai under one per-seat subscription, with no infrastructure to run. Claude Console suits API-first teams who want pay-as-you-go billing. Amazon Bedrock, Google Cloud's Agent Platform and Microsoft Foundry let a team inherit its existing cloud compliance controls and billing.
Models are governed with a few keys. availableModels filters what appears in the model picker, and adding enforceAvailableModels also constrains the auto-selected default. A managed model key only sets the model each session starts with, and developers can still switch with /model. availableModels is the setting that actually locks the choice. maxEffortLevel caps the effort level on every provider. For versions, minimumVersion only stops auto-update from going below a floor, while requiredMinimumVersion and requiredMaximumVersion refuse to start Claude Code at all outside the approved range. forceLoginMethod and forceLoginOrgUUID restrict how developers log in and to which organization.
| Capability | What you get | Availability |
|---|---|---|
| Usage monitoring | OpenTelemetry export of sessions, tools, and tokens | All providers |
| Analytics dashboard | Adoption and contribution metrics on Teams / Enterprise; per-user usage and spend on Console | claude.ai/analytics, or platform.claude.com/claude-code |
| Programmatic reporting | Per-user usage and cost data over an API | Enterprise Analytics API, or Claude Code Analytics API for Console |
| Spend controls | Spend limits and rate limits | Admin settings for Teams / Enterprise, workspace limits for Console |
On third-party clouds, spend is controlled with the cloud's own budget controls or with a Claude apps gateway that sets per-user limits. On data handling, Zero Data Retention is available to qualified Claude for Enterprise accounts. Setting CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC to 1 in managed env turns off Anthropic-bound usage metrics, error reports and surveys on every device.
Checkpoint 6 of 6· Check yourself
Security requires that no developer runs a Claude Code build older than an approved release, even one already installed on their machine. Which key enforces this?
requiredMinimumVersion refuses to start an out-of-range version. minimumVersion only stops auto-update from installing below the floor.
“Stronger than minimumVersion, which only blocks downgrades”Source: code.claude.com
Sources2
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.Bash(git * main) only allows harmless git commands that target the main branch.Why is that wrong?
The * matches any subcommand and any option before it, including git -c, which can run an arbitrary program.
Covered in Permission rules: allow, ask and deny
2.Setting allowedMcpServers to an empty array is the same as leaving it unset, so every server is allowed.Why is that wrong?
An unset allowlist allows all servers. An empty array allows none apart from those that skip the allowlist check.
Covered in Controlling MCP servers, plugins and hooks
3.Setting model in managed settings locks every developer to that model.Why is that wrong?
A managed model only sets the model a session starts with, and developers can still switch with /model. availableModels is the lock.
Covered in Provider, models, versions, spend and usage visibility
Practise it for real
Deploy a file-based managed policy on a test machine and confirm that Claude Code enforces it
1.Create managed-settings.json that denies Read(./.env) and Read(./secrets/**), sets permissions.disableBypassPermissionsMode to "disable", and sets allowManagedPermissionRulesOnly to true.
Why: This is the documented example of a minimal security policy.
You should see: A valid JSON file. Claude Code drops invalid entries in managed settings.
2.Place it at /Library/Application Support/ClaudeCode/managed-settings.json on macOS, or /etc/claude-code/managed-settings.json on Linux or WSL.
Why: File-based policy is read only from the system directory.
You should see: Claude Code reads it at startup and reloads it when the file changes.
3.Start Claude Code in a project and run /status.
Why: /status names the managed source in use and any it skipped.
You should see: The managed settings file appears as the source applied.
4.Add an allow rule to the project's .claude/settings.local.json, then try starting with --dangerously-skip-permissions.
Why: Both are routes a developer might use to loosen policy.
You should see: The local allow rule has no effect because only managed permission rules apply, and bypass mode is disabled.
Stuck? Get a nudge
If /status shows a different source, a higher-ranked managed source, such as server-managed settings or an MDM profile, is winning under first-wins.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://code.claude.com/docs/en/permissionsOfficial docs
“Deny rules prevent Claude Code from using the specified tool.”
↩︎ Permission rules: allow, ask and deny“mcp__puppeteer matches any tool provided by the puppeteer server”
↩︎ Permission rules: allow, ask and deny“It applies only to Bash, PowerShell, and Monitor commands and their child processes.”
↩︎ Permission modes and locking them down“Add a disabledMcpjsonServers entry to reject a .mcp.json server by name in every session type”
↩︎ Controlling MCP servers, plugins and hooks“That includes -c, which makes git run a program you name.”
↩︎ Exam trap 1“Bash(ls*) has no space, so it matches lsof too.”
↩︎ Checkpoint - 2.https://code.claude.com/docs/en/admin-setupOfficial docs
“Make managed settings the only settings source of permission rules. Disable --dangerously-skip-permissions”
↩︎ Permission modes and locking them down“Restrict which MCP servers users can add or connect to, deploy a fixed set”
↩︎ Controlling MCP servers, plugins and hooks“You want Claude Code and claude.ai under one per-seat subscription with no infrastructure to run.”
↩︎ Provider, models, versions, spend and usage visibility“Available to qualified accounts on Claude for Enterprise”
↩︎ Provider, models, versions, spend and usage visibility“Stronger than minimumVersion, which only blocks downgrades”
↩︎ Checkpoint - 3.https://code.claude.com/docs/en/managed-mcpOfficial docs
“Add a server: Claude Code connects it when the updated settings arrive, without a restart.”
↩︎ Controlling MCP servers, plugins and hooks“No servers allowed, apart from those that skip the allowlist check”
↩︎ Exam trap 2“No servers allowed, apart from those that skip the allowlist check”
↩︎ Checkpoint
Also cited
- https://code.claude.com/docs/en/settingsOfficial docs
“the lock is availableModels, which constrains /model, --model, and the model key in your own files”
↩︎ Exam trap 3