Skip to content

[Bug] external_directory and read permissions don't fully canonicalize Windows path variants #427

@Astro-Han

Description

@Astro-Han

What happened?

PawWork's permission system on Windows does not normalize different path spellings that point to the same physical file into a single glob. Calling assertExternalDirectory or the read tool with variants such as D:\foo\bar.txt, d:\FOO\bar.txt, /foo/bar.txt (drive stripped, separators flipped, case folded) produces different permission patterns. Consequences:

  1. Authorizing one variant ("always") does not cover the others, so the same file can prompt for permission repeatedly.
  2. When CWD is on a different drive, a driveless rooted path is interpreted as a different physical file, so the actual coverage of the granted permission diverges from what the user thought they authorized.

#431 (merged) fixed the rooted-but-driveless case for paths that already exist on disk by probing known drive roots in normalizeWindowsAbsolutePath. The remaining variants below are still not handled and are kept tracked here.

Remaining scope (still open)

  • UNC and extended-length prefixes: \\?\D:\foo\bar.txt, \\server\share\... are not folded to the same canonical form as their plain counterparts.
  • Ambiguous multi-drive matches: when a rooted-but-driveless path exists on more than one drive (e.g. C:\foo\bar.txt and D:\foo\bar.txt both exist), the current probe picks the first hit by drive-root iteration order. Behavior should be defined and documented (error, prefer CWD drive, or explicit pick).
  • Write targets / non-existing paths: for paths that do not yet exist (typical for write tools), the probe finds nothing and falls back to win32.resolve, which still binds to CWD's drive. Permission globs for new files therefore still depend on CWD.
  • Drive-root probe caching: windowsDriveRoots() runs per call with up to 26 existsSync syscalls. Hot paths (LSP, ripgrep) that previously assumed normalizePath was pure now pay this cost on Windows. Cache invalidation strategy is a follow-up.

Which area seems affected?

App flow or product behavior

How much does this affect you?

Security or data-loss risk

Steps to reproduce

  1. On Windows, start PawWork or the corresponding dev test environment.
  2. Have the agent call read or any tool guarded by assertExternalDirectory with a mangled path variant from the remaining-scope list above (UNC prefix, ambiguous multi-drive existence, or a non-existing write target with a driveless rooted path) pointing outside the project.
  3. Observe the generated permission request patterns.

Expected: normalized to a canonical Windows path, with permission identical to what an access using the canonical spelling would receive.

Actual: different globs are produced, and for non-existing paths the resolved physical file still depends on the current CWD's drive.

What did you expect to happen?

assertExternalDirectory and read share a single path canonicalize helper. Any Windows path variant (drive case, separator, case fold, /-prefix without drive, UNC \\?\ prefix, ambiguous multi-drive, non-existing write target) is normalized to one canonical form before deriving permission globs, and permission resolution is derived from that canonical form rather than from the raw user-supplied string passed to fs.open.

PawWork version

dev branch, post-#431

OS version

Windows (CI: windows-latest); not validated on a local Windows machine.

Can you reproduce it again?

Yes, every time

Diagnostics

Original failing run: 25303162127, job unit-windows-opencode-server-tools.

Originally failing tests, both fixed by #431:

  • tool.assertExternalDirectory > normalizes Windows path variants to one glob (external-directory.test.ts:118)
  • tool.read external_directory permission > normalizes read permission paths on Windows (read.test.ts:149)

The remaining-scope items above do not yet have dedicated tests; add them when picking this back up.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High priorityappApplication behavior and product flowsbugSomething isn't workingwindowsWindows-specific

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions