Claude Code 프로젝트 설정, 규칙과 도구 허용을 어떻게 고정하나
To lock down Claude Code settings per project, put permissions allow/ask/deny rules in a committed .claude/settings.json (shared) plus a personal .claude/settings.local.json, then verify what actually applies with /permissions and /status.
This is based on current official docs, and keys may change across CLI versions. Subscription pricing is out of scope.
Where do project-level settings live?
Short answer: Put shared team settings in .claude/settings.json, personal exceptions in .claude/settings.local.json, and global preferences in ~/.claude/settings.json.
- User:
~/.claude/settings.json— your personal defaults across every project on this machine. - Shared project:
.claude/settings.json— scoped to a folder/repo; commit it to git to sync with the team. - Project local:
.claude/settings.local.json— your personal overrides for one project. If Claude Code creates it, it’s excluded from git automatically; if you create it by hand, adding it to.gitignoreis recommended. - Managed: an organization’s
managed-settings.json, which developer-level settings can’t easily override.
Installing Claude Code alone does not create any settings file. Running /config, or choosing “don’t ask again” on a permission prompt, can generate one.
Precedence goes Managed → CLI --settings → local → shared project → user, from highest to lowest. However, list-type keys such as permissions.allow are merged across levels rather than fully overridden.
Check what’s actually active under Setting sources in /status. A malformed JSON file shows up there as a Settings Error. For editor autocomplete, add $schema pointing to https://json.schemastore.org/claude-code-settings.json.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": ["Bash(npm run lint)", "Bash(npm run test *)"],
"deny": ["Read(./.env)", "Read(./.env.*)"]
}
}
How do you narrow tool and network access?
Short answer: permissions is evaluated deny → ask → allow, and you narrow network access by pairing a WebFetch(domain:…) allow rule with a Bash deny rule.
Run /permissions to inspect and edit current rules along with where each one came from. Rules take the form Tool or Tool(specifier), e.g. Bash(npm run *), Read(./.env), WebFetch(domain:example.com), or mcp__server__tool.
The evaluation order is deny, then ask, then allow. Once something matches deny, adding a narrower allow rule cannot carve out an exception.
Instructions in CLAUDE.md or a prompt only guide behavior — what actually gets blocked or permitted is enforced by Claude Code’s permissions layer.
permissions.allow and additionalDirectories in a project’s .claude/settings.json only take effect after the workspace trust prompt is accepted. Deny and ask rules, by contrast, apply immediately regardless of trust.
To narrow network access, the general approach is:
- Add something like
Bash(curl *)todeny, and allow only the hosts you need viaWebFetch(domain:allowed-host). - Bash rules alone can be bypassed through other execution paths or
sh -c, so for stricter enforcement you may also want a sandbox network allowlist (not covered in depth here).
Be careful with defaultMode or bypassPermissions outside an isolated environment. Organizations can disable that mode entirely with disableBypassPermissionsMode. This note does not assume any particular default permission mode tied to a specific plan or price tier.
{
"permissions": {
"allow": ["Bash(npm run *)", "WebFetch(domain:docs.anthropic.com)"],
"ask": ["Bash(git push *)"],
"deny": ["Read(./.env)", "Read(./secrets/**)", "Bash(curl *)"]
}
}
How does this line up with Cursor rule files?
Short answer: Cursor rules (.cursor/rules, AGENTS.md, etc.) capture intent and style, while permissions in Claude Code’s settings.json is the layer that enforces tool access.
- Cursor’s project rule files hold coding conventions and workflow instructions. Writing
.mdcrules in detail is out of scope here — see Cursor’s official Rules docs. - In Claude Code, standing instructions live in the CLAUDE.md family of files, while whether an instruction is actually executable is decided by the permissions block in
.claude/settings.json.
A simple way to line the two tools up:
- Put “do this / don’t do that” wording in rule markdown (Cursor rules, CLAUDE.md).
- Put “which Bash/Read/WebFetch/MCP calls are allowed or blocked” in settings permissions.
- Even if the same prohibition (e.g., never read
.env) appears in both places, the actual enforcement comes from permissions’ deny rule.
Syncing AGENTS.md into CLAUDE.md via @AGENTS.md import or a symlink is not covered again here — see the earlier post for that procedure.
FAQ
Is it okay to commit settings.local.json?
It’s meant to be personal, so it’s usually not committed. If it ends up tracked in git, the allow rules inside it can also become subject to the trust prompt.
I added a rule to allow, but Claude Code still asks me. Why?
A higher-precedence deny or ask rule may be matching first, workspace trust may not be accepted yet, or a compound Bash command may not match the rule the way you expect. Check /permissions first to see where each rule comes from.
Is writing “never run curl” in CLAUDE.md enough?
That wording only guides behavior; it isn’t enforced. To actually block it, add the rule to permissions.deny.
What about Claude subscription pricing?
Out of scope for this note. I’m not going to state prices I haven’t verified.