---
title: "How Claude Code Permissions Actually Work: Allow Rules, Deny Rules, and the PreToolUse Hook | Anomity Blog"
description: "How Claude Code evaluates permissions: rules fire deny then ask then allow, and a PreToolUse hook can never override a matching deny or ask."
url: "https://anomity.ai/blog/how-claude-code-permissions-work/"
source: html
---

On this page

- Deny, then ask, then allow
- What the PreToolUse hook adds, and what it can't override
- The four path anchors decide which files a rule covers
- Permission modes change the defaults underneath all of this
- The governance gap: every one of these controls is per-machine

[← Back to blog](https://anomity.ai/blog/)

- Home

- Blog

- How Claude Code Permissions Actually Work: Allow Rules, Deny Rules, and the PreToolUse Hook

![Anomity robot illustrating How Claude Code Permissions Actually Work: Allow Rules, Deny Rules, and the PreToolUse Hook]

Research

# How Claude Code Permissions Actually Work: Allow Rules, Deny Rules, and the PreToolUse Hook

Anomity Research
Security Researcher, Anomity
·
Jun 4, 2026
·
8 min read

Share: Copied

Claude Code's permission system is the boundary between a coding agent that suggests changes and one that runs shell commands, edits files, and reaches the network on your behalf. Most teams configure a few allow rules, maybe a deny rule for `.env`, and move on without knowing the actual evaluation order or what a PreToolUse hook can and cannot do. That gap matters, because the rules are enforced by Claude Code, not by the model: instructions in your prompt or `CLAUDE.md` shape what the agent tries to do, but only the permission layer decides what it is allowed to do.

This post is a mechanism explainer. It assumes you already have the shape of the system in view; if you do not, [how Claude Code works](https://anomity.ai/blog/how-claude-code-works-security-guide/) covers the agentic loop and the five tool categories first. We walk through the order rules are evaluated, how PreToolUse hooks fit in (and where they can't reach), the four path anchors that decide which files a rule covers, and the permission modes that change the defaults. Every behavior here is taken from Claude Code's official permissions and hooks documentation. The same mechanics underpin real-world failures we have written up, including the [Claude Code project-file RCE and token exfiltration in CVE-2025-59536](https://anomity.ai/blog/claude-code-project-file-rce-token-exfil-cve-2025-59536/), where the question of what a local config is permitted to do is exactly the question that decides impact.

## Deny, then ask, then allow

Claude Code splits permission rules into three lists: **allow** lets a tool call proceed without a prompt, **ask** forces a confirmation prompt, and **deny** blocks the call outright. When the agent attempts a tool call, the rules are evaluated in a fixed order: deny first, then ask, then allow. The first list that matches determines the outcome, and specificity does not change the order. A matching ask rule prompts you even when a more specific allow rule also matches the same call. This is the single most important fact about the system, and it is the opposite of the intuition many engineers carry over from firewall or IAM allowlists, where the most specific rule tends to win.

The practical consequence: deny rules are absolute within Claude Code's own enforcement. If a tool is denied at any settings level, no other level can allow it. A user-level deny blocks a project-level allow, and a managed-settings deny cannot be overridden by `--allowedTools` on the command line. How a deny rule is written also matters. A bare tool name like `Bash` removes the tool from the model's context entirely, so Claude never sees it; a scoped rule like `Bash(rm *)` leaves the tool available and blocks only matching calls. The two forms have very different blast radii.

## What the PreToolUse hook adds, and what it can't override

A PreToolUse hook is a shell command Claude Code runs before the permission prompt, on every matching tool call. It receives the tool input on stdin and can return a decision in `hookSpecificOutput`: a `permissionDecision` of `allow`, `deny`, or `ask`, with a `permissionDecisionReason`. This is the runtime check our own [runtime governance at the hook](https://anomity.ai/#runtime-governance) is built around, and it is genuinely powerful. A hook sees the resolved command, can inspect arguments that a static glob can't reliably constrain, and can block calls an allow rule would otherwise wave through. A hook that exits with code 2 stops the call before permission rules are even evaluated. The full event list, matcher syntax, and decision semantics are in [how Claude Code hooks work](https://anomity.ai/blog/how-claude-code-hooks-work/).

What a hook cannot do is loosen the rules. Hook decisions do not bypass permission rules: deny and ask rules are evaluated regardless of what the hook returns. A matching deny rule blocks the call even if the hook returned `allow`, and a matching ask rule still prompts even if the hook returned `allow` or `ask`. The hook also cannot override a managed-policy deny. So the precedence is layered: a blocking hook beats an allow rule, but a deny or ask rule beats a hook. If you are reasoning about why a particular tool call was stopped, check the deny list first, then ask, then any code-2 hook, and only then the allow list.

One caveat worth flagging for anyone hardening a fleet: hook behavior around the `ask` decision has had reported edge cases in the field, and a hook is only as trustworthy as the script it runs and the settings file that registers it. A PreToolUse hook is part of the local configuration, which means it is itself something that should be inventoried and verified rather than assumed. We have seen related trust-boundary failures in agent tooling, including the [GitHub Action bot-actor bypass in Claude Code](https://anomity.ai/blog/claude-code-github-action-bot-actor-bypass/), where the gap between configured intent and actual enforcement is where the risk lives.

## The four path anchors decide which files a rule covers

Read and Edit rules follow gitignore-style matching, but the leading characters of a pattern anchor it to one of four roots. Getting the anchor wrong is the difference between a deny rule that protects every secret on disk and one that protects nothing. The most dangerous mistake is assuming a leading slash means an absolute path: in Claude Code it does not. `/path` is relative to the project root. An absolute filesystem path needs a double slash.

Pattern Anchor Example matches

`//path` Absolute, from filesystem root `Read(//Users/alice/secrets/**)`

`~/path` Home directory `Read(~/.ssh/**)`

`/path` Project root (NOT filesystem root) `Edit(/src/**/*.ts)`

`path` or `./path` Current working directory `Read(*.env)`

A bare filename follows gitignore semantics and matches at any depth, so `Read(.env)` is equivalent to `Read(**/.env) and blocks any .env at or under the current directory, but not one in a parent directory or another project. To block a file everywhere on the machine you need the absolute anchor: Read(//**/.env)`. Deny rules also apply if either a symlink's path or its resolved target matches, so a symlink pointing at a denied file is itself denied. This is where per-machine governance gets brittle: a rule an engineer wrote to protect `~/.ssh` on their laptop says nothing about the same path on anyone else's. We covered the broader pattern in [what we found scanning AI configs](https://anomity.ai/blog/what-we-found-scanning-ai-configs/) and in [securing AI coding agents and CLIs](https://anomity.ai/blog/securing-ai-coding-agents-and-clis/).

## Permission modes change the defaults underneath all of this

Modes set the baseline behavior that the rules then refine. The `default` mode prompts on first use of each tool. `acceptEdits` auto-accepts file edits and common filesystem commands for paths in the working directory. `plan` mode lets the agent read and run read-only commands to explore but not edit source. `bypassPermissions` skips prompts entirely, except those forced by an explicit ask rule and a small circuit-breaker set such as `rm -rf /` and `rm -rf ~`. You set the baseline with `defaultMode` in a settings file.

```
{
"permissions": {
"defaultMode": "default",
"deny": ["Read(.env)", "Bash(git push *)"],
"ask": ["Bash(rm *)"],
"allow": ["Bash(npm run *)", "Bash(git commit *)"]
}
}
```

Two things make modes a governance concern. First, `bypassPermissions` removes nearly every prompt, and while administrators can block it by setting `disableBypassPermissionsMode` to `disable` in managed settings, that control only exists if someone deployed managed settings to the machine in the first place. [The commands cheat sheet](https://anomity.ai/blog/claude-code-commands-cheat-sheet/) lists every permission mode and the flags that reach them. Second, even careful Bash rules are fragile against argument tricks: a rule meant to restrict `curl` to one domain does not survive `-X GET`, a protocol switch, a redirect, or a variable holding the URL. The documentation's own recommendation is to deny network tools and use a PreToolUse hook for real enforcement. The MCP layer compounds this, since stdio servers run as local subprocesses by design, a property we examined in [the Anthropic MCP stdio-by-design RCE](https://anomity.ai/blog/anthropic-mcp-stdio-by-design-rce/).

## The governance gap: every one of these controls is per-machine

Step back and the structure is clear. Allow, ask, and deny rules; PreToolUse hooks; path anchors; permission modes; managed settings: each is a real, well-documented control. What none of them provide is a view across more than one machine. The rules live in `~/.claude/settings.json` and a project's `.claude/settings.json`, the hooks live next to them, and the mode is whatever the local user or a managed policy set. There is no built-in answer to fleet-level questions: which laptops are running in `bypassPermissions`, which projects ship a PreToolUse hook and whether it actually denies anything, which deny rules use a project-relative anchor where an engineer meant an absolute one, and which machines never received managed settings at all. The same per-machine blind spot is what let a single poisoned configuration matter in the [comment-and-control multi-agent prompt injection and credential theft](https://anomity.ai/blog/comment-and-control-multi-agent-prompt-injection-credential-theft/) case.

This is the layer Anomity makes visible. The Endpoint Sensor inventories Claude Code alongside the other AI artifacts on each managed machine, surfaces the permission rules, hooks, and mode that are actually in effect, and where Claude Code exposes the PreToolUse hook, returns an allow, deny, or log decision on each tool call before it runs, with every decision written to a queryable 90-day [audit trail](https://anomity.ai/#outcomes) and routed to your SIEM, Slack, or Jira. It does not replace Claude Code's own enforcement; it gives security a fleet-wide picture of it, so the deny rule you wrote once becomes a policy you can verify everywhere. If you are trying to govern this layer rather than guess at it, [book a demo](https://anomity.ai/#early-access).

Share: Copied

## Related

[Research ### Anthropic's GLM-5.3 Study: Near-Frontier Exploit Skills Now Download Without Safeguards Anthropic found open-weight GLM-5.3 nearly matches Claude Mythos Preview at exploits, and its refusals strip off for about $4,400. What changes for you. Anomity Research · Oct 2, 2026 · 4 min](https://anomity.ai/blog/glm-5-3-open-weight-cyber-capability-local-models/)

[Research ### GPT-6.1 Sol's System Card: Block One Channel and the Agent Tries Another 23.5% of the Time OpenAI's GPT-6.1 Sol report measures agents persisting past blocks, messaging unknown peers and misreporting work. Those numbers are an enforcement spec. Anomity Research · Oct 2, 2026 · 5 min](https://anomity.ai/blog/gpt-6-1-sol-system-card-agent-behavior/)

[Research ### GTIG's AI Vulnerability Data: Half of 2026's AI-Software CVEs Hit Agent Frameworks GTIG tracked 2,076 AI-software CVEs since 2025: 782 this year in agent frameworks, 212 in inference servers. Patching them starts with finding them. Anomity Research · Oct 2, 2026 · 5 min](https://anomity.ai/blog/gtig-ai-software-vulnerabilities-agent-frameworks-2026/)

## Structured data

```json
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "How Claude Code Permissions Actually Work: Allow Rules, Deny Rules, and the PreToolUse Hook",
  "description": "How Claude Code evaluates permissions: rules fire deny then ask then allow, and a PreToolUse hook can never override a matching deny or ask.",
  "datePublished": "2026-06-04",
  "dateModified": "2026-06-04",
  "author": {
    "@type": "Person",
    "name": "Anomity Research",
    "jobTitle": "Security Researcher, Anomity"
  },
  "publisher": {
    "@type": "Organization",
    "name": "Anomity",
    "url": "https://anomity.ai/",
    "sameAs": [
      "https://www.linkedin.com/company/anomity",
      "https://github.com/Anomity-ai",
      "https://www.wikidata.org/wiki/Q140763940"
    ],
    "logo": {
      "@type": "ImageObject",
      "url": "https://anomity.ai/icon-512.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://anomity.ai/blog/how-claude-code-permissions-work/"
  },
  "image": {
    "@type": "ImageObject",
    "url": "https://anomity.ai/assets/blog/covers/posts/how-claude-code-permissions-work.jpg",
    "width": 1200,
    "height": 630
  },
  "articleSection": "Research",
  "url": "https://anomity.ai/blog/how-claude-code-permissions-work/"
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://anomity.ai/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog",
      "item": "https://anomity.ai/blog/"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "How Claude Code Permissions Actually Work: Allow Rules, Deny Rules, and the PreToolUse Hook",
      "item": "https://anomity.ai/blog/how-claude-code-permissions-work/"
    }
  ]
}
```
