`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)

Status Open
Reported on v2.1.212
Maintainer reply None cached
Activity 0 comments · opened Jul 18, 2026

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

  1. In settings.json, add a deny rule for a long-form Windows path, e.g.: "permissions": { "deny": ["Read(//c/Users/<user>/AppData/Local/Microsoft/**)"] }
  1. 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

  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.

  1. 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.

  1. (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).

  1. (Filename variant) Add "Read(//**/credentials.json)" as a deny rule. Create a file literally named credentials.json anywhere.

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):

  1. 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.

  1. 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.

View original on GitHub ↗