One file, three lists, deny-first evaluation
Claude Code's permission model is small on purpose: every tool call is matched against an allow, ask, or deny pattern, in that priority order, first match wins. Read tools always pass. Bash and Edit/Write don't.
Patterns are glob-style on tool specifiers: Bash(npm run *), Bash(git diff *), Read(./.env), WebFetch(domain:api.github.com). The pattern Bash alone means "any bash"; Bash(*) is equivalent. Wildcards work mid-string too, so Bash(* --dry-run) is a real, useful rule.
Settings layer like CLAUDE.md: enterprise managed → user global → project shared → project local (gitignored). That ordering is deliberate — security teams pin rules at the top, individuals customize at the bottom.