Checklist · Intermediate · 15 min
Before you give Claude Code permissions: a security checklist
Ten checks before giving Claude Code more permissions or opening it in someone else's repo: modes, deny, ask and allow, trust, sandbox and bypass.
Tested with Claude Code 2.1.316 · reviewed
See the files on GitHub Free · MIT license
What it's for
A list of ten checks to run before giving Claude Code more permissions or opening it in a repository that isn't yours. It organizes what the official docs say: where each rule lives and which one wins (deny → ask → allow, in any scope), what a project applies before and after you accept folder trust, why auto and bypassPermissions don't work from project settings, why blocking curl doesn't cut off the network and how to lock yourself out of bypass mode. Each item comes with its why and how to check it. For rule syntax, see the permissions recipe.
The checklist
- Why: the mode decides what gets approved without asking you. Since version 2.1.283, if your account and model support it, interactive sessions in the terminal and in VS Code start in auto: a separate classifier reviews commands and network requests in the background and blocks what it considers dangerous. It's convenient, but it isn't "risk-free": the classifier decides for you. In Manual mode (
default), Claude Code asks for permission the first time it uses each tool, except for a fixed set of read-only commands (ls,cat,git status…). - How to check it: look at the mode indicator at the bottom of the terminal and change it with
Shift+Tab. To set a different starting mode, putpermissions.defaultModein~/.claude/settings.json, or start withclaude --permission-mode default. In a project you don't know, start inplanor Manual. - The order: the starting mode comes from the
--permission-modeoption (or--dangerously-skip-permissions), then fromdefaultModein a settings file and finally from the factory default. The VS Code extension has its own setting,claudeCode.initialPermissionMode, and never reads the mode from project settings.
- Why: the mode decides what gets approved without asking you. Since version 2.1.283, if your account and model support it, interactive sessions in the terminal and in VS Code start in auto: a separate classifier reviews commands and network requests in the background and blocks what it considers dangerous. It's convenient, but it isn't "risk-free": the classifier decides for you. In Manual mode (
- Why: the same rule can live in four places and they add up. From highest to lowest priority: your organization's managed settings (can't be overridden), command-line options, local (
.claude/settings.local.json, just you in this project, out of git), project (.claude/settings.json, the whole team) and user (~/.claude/settings.json, you in every project). The allow, ask and deny lists don't overwrite each other across files: they merge. - The golden rule: rules are evaluated in the order deny → ask → allow and the first match wins, even if another rule is more specific. A deny in any scope blocks even if another scope allows it: a deny in your user file beats a project allow, and vice versa. An allow can't carve an exception out of a deny: with
deny: Bash(aws *),allow: Bash(aws s3 ls)does nothing. And an ask asks even if there's a more specific allow. - How to check it: in the session,
/permissionsshows each rule with the file it comes from, and/statusshows which settings files were loaded. Put the denies you want everywhere (your~/.ssh,~/.aws) in your user file: that way no repo can take them away from you.
- Why: the same rule can live in four places and they add up. From highest to lowest priority: your organization's managed settings (can't be overridden), command-line options, local (
- Why: when you open
claudein a new folder, the trust dialog appears. Until you accept it, the allow rules and additionalDirectories in the project's.claude/settings.jsondon't apply (they grant permissions); deny and ask rules do, because they only restrict. The dialog lists what the folder would grant you. Accept it only after reading it. - What almost nobody knows: according to the official table "What runs before you trust a folder", with
claude -por the SDK, in a folder you never trusted, Claude Code does use the hooks in the project settings, theenvblock, helpers likeapiKeyHelperand theallowed-toolsof project skills, and it connects without asking to the servers in.mcp.json. We checked it on this PC: a SessionStart hook in a test repo created a file withclaude -pbefore the CLI even said "Not logged in". - How to check it: before launching anything in someone else's repo, look at
.claude/settings.json,.claude/settings.local.json,.claude/skills/,.claude/agents/and.mcp.json(the repository'sen/revisar-repo-ajeno.shscript does this without running anything). If you're going to useclaude -pthere, add--setting-sources user(doesn't read the project settings or.mcp.json),--bare(doesn't read the project's hooks, skills, commands, subagents, plugins or servers; the project'senvstill applies) or--settings '{"disableAllHooks": true}'. All three prevented the hook in our test. Putting it only in your user settings isn't enough: the project has higher priority.
- Why: when you open
# Real test: someone else's repo runs its hook with claude -p (Nov 1, 2026, Claude Code 2.1.316)
# Test repo with a SessionStart hook in .claude/settings.json that touches a file
$ claude -p "say hello" < /dev/null
Ignoring 2 permissions.allow entries from .claude/settings.json: this workspace has not been trusted. ...
Not logged in · Please run /login
$ ls ../HOOK-RAN
../HOOK-RAN # the hook ran: no trust and no logged-in session
# The three ways to prevent it (the hook did NOT run in any of them):
$ claude -p "say hello" --setting-sources user
$ claude -p "say hello" --bare
$ claude -p "say hello" --settings '{"disableAllHooks": true}'
- Why: it's the most common cause of "Claude keeps asking even though I allowed it in the project". Trust is stored per git repository root (it covers the whole repo except nested repos, such as submodules, which ask for their own). If you start Claude in your home folder, trust lasts only for that session and isn't saved. A
claude -pnever shows the dialog. - How to check it: run
claude -p "hello"orclaude doctorin the folder. If you seeIgnoring 2 permissions.allow entries from .claude/settings.json: this workspace has not been trusted. Run Claude Code interactively here once and accept the trust dialog, or set projects["…"].hasTrustDialogAccepted: true in …/.claude.json.on stderr, your project allow rules are on hold. - Fix: open
claudeinteractively once and accept. EditinghasTrustDialogAcceptedby hand in~/.claude.jsonis an alternative the docs mention, but only use it on folders you've already reviewed. - One more detail:
.claude/settings.local.jsonnormally applies without trust because it's yours; but if it's tracked in git or.claudeis a symlink, Claude Code treats it as part of the repository and holds back its rules.
- Why: it's the most common cause of "Claude keeps asking even though I allowed it in the project". Trust is stored per git repository root (it covers the whole repo except nested repos, such as submodules, which ask for their own). If you start Claude in your home folder, trust lasts only for that session and isn't saved. A
- Why: so that a repo can't decide for you how much gets approved automatically, Claude Code ignores
defaultMode: "auto"and"bypassPermissions"when they're in.claude/settings.jsonor.claude/settings.local.json. With auto, the factory default is used (not thedefaultModein your user file); with bypass, the session starts in Manual. The other modes (default,acceptEdits,plan,dontAsk) do apply from the project in the terminal. Cloud sessions also ignore bypass and dontAsk from the files. - How to check it: if you set auto in the project and the session starts in Manual with no error, this is why. Move the setting to
~/.claude/settings.jsonor use--permission-mode auto. - And bypass mode:
bypassPermissions(same as--dangerously-skip-permissions) skips the prompts, including writes to protected paths like.gitand.claude. The docs limit it to containers or virtual machines where it can't do harm, and on Linux they recommend not running it as root. Not even in that mode are ask rules, rm deletions of critical paths orAskUserQuestionapproved automatically.
- Why: so that a repo can't decide for you how much gets approved automatically, Claude Code ignores
- Why: the most common way to end up in bypass is habit or a copied alias. You can prevent it yourself:
permissions.disableBypassPermissionsMode: "disable"works from any scope, not only managed settings, and a user can put it in their own file so they can't enter bypass. The same goes forpermissions.disableAutoMode: "disable"if you'd rather not delegate to the classifier; with it,autodisappears from theShift+Tabcycle and a session launched with--permission-mode autostarts in Manual. - How to check it: add the key to
~/.claude/settings.json, validate the JSON withpython3 -m json.tool ~/.claude/settings.jsonand startclaude --dangerously-skip-permissions: according to the docs it should no longer enter bypass (we couldn't see it without a logged-in session). In an organization, put it in managed settings, where nobody can remove it.
- Why: the most common way to end up in bypass is habit or a copied alias. You can prevent it yourself:
{
"permissions": {
"disableBypassPermissionsMode": "disable"
}
}
- Why: the official docs are clear:
curlandwgetaren't auto-approved by default (in Manual they ask like any non-read-only command), and you can put them inpermissions.deny. But a deny rule matches the command as written:deny: Bash(curl *)stopscurl https://…and doesn't stop/usr/bin/curl https://…,sh -c 'curl …'or a Python script that opens a connection. And allowing onlyWebFetch(domain:github.com)isn't enough either: if Bash is allowed, Claude can use any other network tool. - The real barrier is the sandbox: with
/sandbox(orsandbox.enabled: true), Bash commands and their child processes run isolated; on Linux, in a separate network namespace with no way out, reaching only the domains insandbox.network.allowedDomainsthrough a proxy. On Linux and WSL2 it needsbubblewrapandsocat(sudo apt-get install bubblewrap socat); on Ubuntu 24.04 or later, AppArmor can stop bubblewrap from starting, and/sandboxtells you how to fix it. - How to check it: open
/sandbox; if you only see the Dependencies tab, something is missing. Watch out: if the sandbox can't start, commands run without it by default; setsandbox.failIfUnavailable: trueif you want Claude Code to refuse to start.
- Why: the official docs are clear:
- Why: the sandbox wraps shell commands, not everything else. According to the sandbox docs, these stay outside: the Read, Edit, Write, WebFetch and WebSearch tools (they follow your permission rules: a sandbox
denyReaddoesn't stop Read, andallowedDomainsdoesn't limit WebFetch), hooks, local MCP servers, plugin monitors, the statusline andapiKeyHelper(they run with all your permissions), what you type yourself with!, the commands inexcludedCommandsand the unsandboxed retries Claude asks for after a failure. - Also: by default the sandbox can read almost the whole machine, including
~/.sshand~/.aws/credentials, and it inherits your environment variables, secrets included. - How to check and close it: add
sandbox.filesystem.denyReadfor your credentials andpermissions.denywithRead(~/.ssh/**)andRead(~/.aws/**)for the file tools. To stop Claude from asking to run outside,sandbox.allowUnsandboxedCommands: false("Strict sandbox mode" in the Overrides tab). To put everything behind a single barrier, run Claude Code inside a container, a virtual machine or a dev container.
- Why: the sandbox wraps shell commands, not everything else. According to the sandbox docs, these stay outside: the Read, Edit, Write, WebFetch and WebSearch tools (they follow your permission rules: a sandbox
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"network": {
"allowedDomains": ["github.com", "*.github.com", "registry.npmjs.org"]
},
"filesystem": {
"denyRead": ["~/.ssh", "~/.aws"]
}
}
}
- Why: in Manual, Claude Code asks before its file tools read or write outside the folder where you opened it, but a Bash command you approve can write anywhere your user can reach. Since version 2.1.314 there's
permissions.blockWritesOutsideWorkingDirectories: Write, Edit and NotebookEdit refuse to write outside the working directories in every mode (we covered it in this news item). There's alsopermissions.blockReadsOutsideWorkingDirectories(since 2.1.257) for reads, which makes Claude ask even in auto and bypass. - How to check it: add it to
~/.claude/settings.jsonand ask Claude to edit a file in another folder: according to the docs it should refuse. If you need it to read another repo, add it with/add-diror--add-dirinstead of loosening everything. Remember that allow rules don't approve writes to protected paths (.git,.claude…): in Manual and acceptEdits they always ask. - For Bash: the barrier is the sandbox again, which by default only allows writes in the working directory, a temp folder and the folders you add.
- Why: in Manual, Claude Code asks before its file tools read or write outside the folder where you opened it, but a Bash command you approve can write anywhere your user can reach. Since version 2.1.314 there's
- Why: every "Yes, and don't ask again" is saved as an allow rule in
.claude/settings.local.json. Over time you pile up broad permissions without noticing, and some are equivalent to running anything:Bash(python3 *),Bash(node *),Bash(bash *),Bash(npx *)orBash(git *)(the last one includesgit -c core.fsmonitor=…). - How to check it: once a month open
/permissions, review the rules tab and the auto mode recent denials tab, and delete what you don't need. Replace wildcards with exact commands (Bash(python3 -m pytest *)instead ofBash(python3 *)). - And remember who's in charge: Claude Code enforces the rules, not the model. What you write in CLAUDE.md or in the prompt guides what Claude tries, but it doesn't change what's allowed. If something must always hold, it goes in a deny rule, a PreToolUse hook (a hook that exits with code 2 blocks even with an allow; and an allow returned by the hook doesn't override a deny) or managed settings. Be careful with the mods you install: without managed settings or a Team or Enterprise session, a mod can approve a call that a deny rule rejects.
- Why: every "Yes, and don't ask again" is saved as an allow rule in
Your personal locks, in a single file that applies to all your projects (~/.claude/settings.json):
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"disableBypassPermissionsMode": "disable",
"blockWritesOutsideWorkingDirectories": true,
"deny": [
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Read(~/.config/gh/**)"
]
}
}
And the review of someone else's repo before opening Claude in it:
# What Claude configuration does it bring?
ls -la .claude/ .mcp.json CLAUDE.md AGENTS.md 2>/dev/null
# Hooks, env and helpers that would run
python3 -m json.tool .claude/settings.json 2>/dev/null | grep -nE '"(hooks|env|apiKeyHelper|allow|additionalDirectories|enableAllProjectMcpServers)"'
# Skills with allowed-tools (they don't depend on trust)
grep -rn 'allowed-tools' .claude/skills 2>/dev/null
# MCP servers it would ask to connect
claude mcp list # the ones in .mcp.json show "⏸ Pending approval" until you approve them
The repository includes this review as en/revisar-repo-ajeno.sh (it only reads files: instead of claude mcp list it prints the .mcp.json) plus the two sample JSON files in en/ejemplos/. If you're starting a project from scratch, the project template already has these permissions in place.
Common mistakes
- Claude keeps asking even though you put the allow in the project → the folder's trust hasn't been accepted (or you started in your home folder) → open
claudein the repo root once and accept the dialog. - You set
defaultMode: "auto"in.claude/settings.jsonand the session starts in Manual → Claude Code ignores auto and bypass from project settings → move it to~/.claude/settings.jsonor use--permission-mode auto. - You blocked
Bash(curl *)and think you cut off the network → the rule matches text and doesn't stop/usr/bin/curlorsh -c→ turn on the sandbox withallowedDomainsandfailIfUnavailable. - An
allow: Bash(aws s3 ls)doesn't work → there's adeny: Bash(aws *)in some scope, and deny always wins → remove or narrow the deny. - The deny does nothing → invalid JSON, a badly written path or a command that doesn't match literally →
claude doctor(look for "Invalid settings") and/permissions. - You run
claude -pin a cloned repo "just to take a look" → its hooks and MCP servers run without trust → review.claude/first and use--setting-sources user,--bareordisableAllHooks.
What we tested and what's pending
On this PC (Linux, Claude Code 2.1.316), with an isolated configuration (CLAUDE_CONFIG_DIR) and without logging in: in a test repo with a project allow, claude -p and claude doctor show "Ignoring 2 permissions.allow entries… not been trusted", and the warning goes away after setting hasTrustDialogAccepted; a project SessionStart hook ran with claude -p without trust and before "Not logged in", and with --setting-sources user, --bare and --settings '{"disableAllHooks": true}' it didn't run (we repeated it on Nov 1 with the same result); a .mcp.json auto-approved by the repo shows "⏸ Pending approval" in claude mcp list. We couldn't test: the network sandbox (this PC has bwrap but not socat), that bypass and auto are ignored from the project, or disableBypassPermissionsMode and blockWritesOutsideWorkingDirectories in a real session (without a logged-in session the CLI stops earlier at "Not logged in"). Those points rely on the official docs as of Nov 1, 2026.
FAQ
Why does Claude Code ignore the deny in my settings.json?
It's almost always one of three things: the JSON isn't valid (comments or trailing commas; claude doctor shows "Invalid settings"), the path isn't written the way you think (in the user file, /path starts at ~/.claude/; use ~/path or //absolute/path) or the command doesn't match literally (Bash(curl *) doesn't stop /usr/bin/curl). Check with /permissions which file each rule comes from.
Can a repo approve its own MCP servers?
Not in an interactive session: enableAllProjectMcpServers or enabledMcpjsonServers in the repo's .claude/settings.json are ignored in an untrusted folder, and claude mcp list shows them as "⏸ Pending approval". We checked it on this PC. But with claude -p, in a folder you never trusted, the servers in .mcp.json connect without asking, approved or not; use --setting-sources user or --bare in other people's repos.
Is auto mode safe for working on my machine?
It's much safer than bypassPermissions, because a classifier reviews commands and network requests and your ask and deny rules still apply. But a model decides, not you, and the docs recommend adding a sandbox or a container as an extra layer of defense. For code or repos you don't know, start in plan or Manual.
Is --dangerously-skip-permissions in a container okay?
That's what it exists for: the docs point to containers, virtual machines or the sandbox runtime, and on Linux and macOS as a non-root user. Even so, if the container has your credentials or network access, a mistake or a prompt injection can use them.
What's the difference between deny and a PreToolUse hook?
Claude Code enforces both. Deny is declarative and matches the tool pattern; the hook is your own program that can look at anything (for example, which branch you're on) and block with exit 2. The hook blocks even if there's an allow, and an allow returned by the hook doesn't override a deny or an ask.