[FEATURE] specificity-aware permission precedence (let a narrow allow override a broad deny)
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Claude Code's permission engine resolves rule conflicts with a fixed precedence: a deny always beats an allow, no matter how broad or narrow each rule is. This makes the common least-privilege pattern — block a whole tool, carve out a narrow safe exception — impossible to express.
Reproduction
settings.json:
{
"permissions": {
"deny": ["Bash(aws:*)"],
"allow": ["Bash(aws * describe-*)"]
}
}
Expected: read-only calls like aws ec2 describe-instances are allowed; everything else under aws (e.g. aws ec2 terminate-instances) is denied.
Actual: the broad deny swallows the specific allow — every aws call is denied, including the whitelisted read-only ones.
Request
Support specificity-aware precedence, where the most specific matching rule wins in either direction:
- a narrow allow punches through a broad deny
- a narrow deny punches through a broad allow
- on equal specificity, fall back to most-restrictive-tier-wins (deny > ask > allow)
This is strictly more expressive than the current model and enables real allow-list-with-exceptions policies. Making it opt-in (e.g. a permissions.precedence: "specificity" setting) would preserve backward compatibility.
Proposed Solution
Workaround
I built a PreToolUse hook, permcheck (https://github.com/saleem-mirza/permcheck), that implements exactly this (most-specific-rule-wins, fail-closed). It works, but this belongs in the native model — which is the actual security boundary — rather than in a hook layered on top.
Alternative Solutions
An update in native security model is highly desired
Priority
High - Significant impact on productivity
Feature Category
Configuration and settings
Use Case Example
Guard destructive git while allowing everyday git
Let the agent stage, commit, and pull freely, prompt before pushing, and hard-block history-rewriting or destructive commands:
{
"permissions": {
"deny": [
"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git reset --hard:*)",
"Bash(git clean:*)"
],
"ask": ["Bash(git push:*)"],
"allow": ["Bash(git:*)"]
}
}
| Command | Desired | Native result |
|---|---|---|
| git add -A | ✅ allow | ✅ allow |
| git commit -m "fix" | ✅ allow | ✅ allow |
| git push origin main | ⚠️ ask | ⚠️ ask |
| git push --force origin main | ❌ deny | ✅ allow* |
\* Under native precedence the broad Bash(git:*) allow and the Bash(git push:*) ask both apply, but there's no reliable way for the narrow --force deny to win over the broad allow in the general case — you can't layer "allow git, but deny this one dangerous variant" cleanly. Specificity-aware precedence makes the longest-matching git push --force deny authoritative.
Restrict web access to trusted domains
Block the web tools broadly, allow only vetted internal/docs domains:
{
"permissions": {
"deny": ["WebFetch", "WebSearch"],
"allow": [
"WebFetch(domain:docs.internal.company.com)",
"WebFetch(domain:github.com)"
]
}
}
| Call | Desired | Native result |
|---|---|---|
| WebFetch docs.internal.company.com/... | ✅ allow | ❌ deny |
| WebFetch github.com/... | ✅ allow | ❌ deny |
| WebFetch random-blog.example/... | ❌ deny | ❌ deny |
The broad deny on WebFetch swallows the domain-scoped allows today, so you can't express "no web access except this short allow-list" — exactly the prompt-injection guardrail teams
want.
Protect secrets even through obfuscation
Deny reads of sensitive files while allowing general file reads:
{
"permissions": {
"deny": [
"Read(/**/.env*)",
"Read(/**/.ssh/**)",
"Read(/**/*.pem)"
],
"allow": ["Read(/**)"]
}
}
The narrow secret-file denies must win over the broad Read(/**) allow — which specificity-aware precedence guarantees, but the native "broad allow, narrow deny" combination can't be
expressed as a clean least-privilege policy.
Additional Context
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗