`permissions.deny` path matching does not canonicalize Windows 8.3 short filenames, and wildcard deny patterns are not enforced consistently across tools (Read/Edit vs Glob)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
On Windows, permissions.deny entries in settings.json are matched against the literal path string supplied to a tool call. Windows itself can resolve a single file or directory through multiple equivalent path spellings (long name vs. 8.3 "short name" alias). Because the permission matcher does not canonicalize paths before comparison, a deny rule written against the long-form path can be bypassed simply by addressing the same file through its short-form alias. Additionally, wildcard (*) deny patterns that do work correctly for the Read and Edit tools are not honored by the Glob tool, allowing directory enumeration of nominally-denied trees.
What Should Happen?
A permissions.deny rule written against a resource's long-form path should also block access to that same resource via any other valid Windows path spelling for it - specifically its 8.3 short-name alias (e.g. MICROS~1 for Microsoft). This should hold consistently across all tools that consult permissions.deny (Read, Edit, and Glob).
Error Messages/Logs
There is no error - that is the bug. The bypassed calls succeed silently and return real file/directory data instead of being denied.
Example (paths anonymized):
- Read(C:\Users\<user>\AppData\Local\Microsoft\**) -> correctly denied: "File is in a directory that is denied by your permission settings."
- Read(C:\Users\<user>\AppData\Local\MICROS~1\**) -> same resource, short-name path -> SUCCEEDS, returns file contents (should be denied)
- Glob(C:\Users\<user>\AppData\Local\MICROS~1, "**/*") with deny rule "Read(.../MICROS~*/**)" -> SUCCEEDS, enumerated 14,000+ files (should be denied)
Steps to Reproduce
- In
settings.json, add a deny rule for a long-form Windows path, e.g.: "permissions": { "deny": ["Read(//c/Users/<user>/AppData/Local/Microsoft/**)"] }
- Find the Windows 8.3 short-name alias for that same directory (any name >8 chars, or containing a leading dot, will have one).
This can be queried via the Win32 GetShortPathName API, e.g. from PowerShell:
Add-Type -TypeDefinition '...GetShortPathName...' (see full repro script in linked write-up)
-> e.g. Microsoft resolves to MICROS~1
- Ask Claude to Read a file under the LONG-form path, e.g.
C:\Users\<user>\AppData\Local\Microsoft\Edge\User Data\Local State
-> correctly denied.
- Ask Claude to Read the SAME file under the SHORT-form path, e.g.
C:\Users\<user>\AppData\Local\MICROS~1\Edge\User Data\Local State
-> bypasses the deny rule, file contents are returned.
- (Glob-specific variant) Add a wildcard deny rule instead: "Read(//c/Users/<user>/AppData/Local/MICROS~/*)".
Ask Claude to Glob the short-form path with pattern "**/*" -> enumerates the full directory tree despite the matching deny rule
(Read against the same path IS correctly denied - the defect is specific to Glob's wildcard handling).
- (Filename variant) Add "Read(//**/credentials.json)" as a deny rule. Create a file literally named
credentials.jsonanywhere.
Its 8.3 short name will be CREDEN~1.JSO (base >8 chars, extension >3 chars). Reading it via CREDEN~1.JSO bypasses the deny rule
and returns the file's contents.
Claude Model
Sonnet (default)
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.212 (Claude Code)
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
PowerShell
Additional Information
This report bundles 3 related symptoms of the same root cause (permissions.deny matches literal path strings without OS-level
canonicalization on Windows):
- Wildcard deny patterns (e.g. "MICROS~*") correctly block Read/Edit but do NOT block Glob against the same short-name path -
confirmed via a control test where an exact-literal (non-wildcard) deny rule WAS correctly honored by Glob, isolating the defect to
wildcard-pattern handling specifically within Glob.
- Windows generates 8.3 short-name aliases even for names <=8 characters when they contain characters invalid in classic 8.3 names
(e.g. a leading dot: directory ".aws" -> short name "AWS~1"). Any length-based heuristic for deciding which paths need short-name
defenses is therefore unsafe.
Related, unverified in our testing but sharing the same root cause and worth investigating together: case-insensitivity handling,
UNC-style paths (\\localhost\c$\..., \\?\C:\... extended-length prefix), symbolic links/NTFS junctions, and trailing dot/space or
Unicode normalization variants.
Suggested fix: canonicalize (resolve to long/real form) any path argument to Read/Edit/Glob before evaluating
permissions.allow/permissions.deny on Windows. This would close the entire defect family at once rather than requiring per-path
enumeration workarounds.