Skip to content

fix(permissions): treat Windows \\?\ verbatim paths as equivalent#35096

Merged
littledivy merged 3 commits into
mainfrom
orch/divybot-553
Jun 12, 2026
Merged

fix(permissions): treat Windows \\?\ verbatim paths as equivalent#35096
littledivy merged 3 commits into
mainfrom
orch/divybot-553

Conversation

@divybot

@divybot divybot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

On Windows, requesting a path through its \\?\ verbatim (extended-length) form — e.g. \\?\C:\foo — was permission-checked as a distinct path from the regular C:\foo. As a result, a directory already granted read/write access would unexpectedly trigger a new permission prompt:

> deno repl --allow-read=.
> Array.from(Deno.readDirSync(Deno.cwd()))            // no prompt
[]
> Array.from(Deno.readDirSync('\\\\?\\' + Deno.cwd())) // unexpected prompt
✅ Granted read access to "\\?\C:\Users\Student\Empty".
[]

This also breaks node polyfills that hand \\?\-prefixed paths to fs ops (e.g. _fs_chmod.ts, see wojpawlik/deno2node#39).

Cause

The path descriptors in deno_permissions build their comparison key with deno_path_util::normalize_path, which walks path.components(). Rust parses \\?\C:\foo as a Component::Prefix(VerbatimDisk), and normalize_path preserves that verbatim prefix, so \\?\C:\foo and C:\foo never compare equal. The existing is_windows_device_path branch only handled \\.\ device-namespace paths.

Fix

Strip the verbatim prefix via dunce::simplified before normalization, at the three path-descriptor construction sites (PathQueryDescriptor::new, PathQueryDescriptor::new_known_absolute, PathDescriptor::new_known_cwd).

dunce::simplified only strips the prefix when the regular form is genuinely equivalent — it refuses paths containing ./.. components (taken literally in verbatim mode), reserved DOS device names (CON, NUL, COM1, …), or paths that exceed the legacy length limit. Those forms are not the same file as their stripped counterpart, so this conservative behavior avoids over-granting. On non-Windows platforms the helper is a no-op, so behavior there is unchanged.

A #[cfg(windows)] unit test (check_path_verbatim_prefix) covers both directions (grant regular → request verbatim, and grant verbatim → request regular) plus a negative case.

Closes #18597

Closes denoland/divybot#553

divybot and others added 3 commits June 10, 2026 11:56
On Windows, requesting a path via its `\\?\` verbatim (extended-length)
form, e.g. `\\?\C:\foo`, was permission-checked as distinct from the
regular `C:\foo`, so a path already granted access would unexpectedly
trigger a new permission prompt (#18597).

The path descriptor normalization in `deno_permissions` builds its
comparison key with `deno_path_util::normalize_path`, which preserves the
`\\?\` verbatim prefix that Rust parses as a `Prefix::VerbatimDisk`
component. The two forms therefore never compared equal.

Strip the verbatim prefix via `dunce::simplified` before normalization at
the three path-descriptor construction sites. `dunce::simplified` only
strips the prefix when the regular form is genuinely equivalent (it
refuses paths with `.`/`..` components, reserved DOS device names, or that
exceed the legacy length limit), so this does not over-grant.

Co-Authored-By: Divy Srivastava <[email protected]>
The TestPermissionDescriptorParser stub builds path descriptors directly
without normalization or verbatim-prefix stripping, so testing through
`check_open` bypassed the fix entirely. Test the descriptor construction
functions (`PathDescriptor`/`PathQueryDescriptor::new_known_absolute`)
directly instead, which is what the real runtime parser uses.

Co-Authored-By: Divy Srivastava <[email protected]>
@littledivy
littledivy merged commit d535582 into main Jun 12, 2026
137 checks passed
@littledivy
littledivy deleted the orch/divybot-553 branch June 12, 2026 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Permissions incorrectly treat \\?\ paths as distinct

2 participants