Repository navigation
fix(filesystem): open files on a Windows mapped or subst drive - #24243
JahyunBaek wants to merge 3 commits into
Conversation
… path authorizeExternalPath remembered the JS realpathSync result, which keeps a Windows mapped (net use) or subst drive letter, while resolveAuthorizedPath re-checks with the native realpath, which resolves that drive to its UNC or target path. The two never matched, so a file granted on such a drive was then denied with "path resolves outside allowed directories" - the reason terminal file links on a mapped drive do nothing. Also remember the native realpath. It names the same file or folder that was granted, so nothing new becomes reachable, and every path allowed before is still allowed. Fixes stablyai#23018 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADAPnmpPBMctKbwkj2i9NS
authorizeExternalPath runs synchronously on the main thread. When the JS realpath has already thrown - a missing file or an unreachable network share - the native call fails the same way, so on a disconnected drive it only added a second wait. Ask for it only after the JS realpath succeeds; the mapped-drive case this is for is one where the JS realpath succeeds and keeps the drive letter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADAPnmpPBMctKbwkj2i9NS
…helper
The test's drive mapping used `relative(...).startsWith('..')`, which treats
a child named `..notes` as outside the drive. Use isDescendantOrEqual, the
check the code under test uses, and reword the grant comment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADAPnmpPBMctKbwkj2i9NS
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Native-realpath grant on Windows mapped/
substdrives —authorizeExternalPathnow also recordsresolve(realpathSync.native(resolvedTarget))inside the existingtry, so the grant matches the native canonical form thatresolveAuthorizedPathcompares against. - New regression test (
filesystem-auth-external-grant-native-realpath.test.ts) — models a JS/native realpath split with the sharedisDescendantOrEqualhelper, covering file grant, folder grant, the shared-tryskip, and non-widening to siblings.
I verified the premise that the read check uses a native realpath (fs.promises.realpath calls the binding.realpath binding in lib/internal/fs/promises.js), confirmed the test passes on the branch and fails with the exact Access denied error when the two added lines are reverted (2 of 4 cases), and checked the LRU-bound test still holds since nonexistent flood paths throw before the native call. The added entry is the canonical name of the just-granted object, so reachability does not widen.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Repository guideline files applied to this review (1)No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (2)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthrough
Priority: ➖ Normal Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to The change addresses mapped-drive file access while preserving the existing authorization boundaries. No actionable merge-blocking risk is identified; normal checks should complete before merging. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change aligns file permissions with Windows canonical paths while preserving path-scope checks. No new authorization bypass was demonstrated. Drive-remapping behavior and the trust placed in grant callers remain incompletely established. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |

ELI5
When you open a file from outside your projects — say, a path printed in the terminal — Orca first gives itself permission for that one file, then checks that permission before it reads the file. On Windows, a mapped network drive (
X:fromnet use) or asubstdrive has two names for the same file:X:\docs\page-1.png, and the real location behind it. The permission was written down under the first name and checked under the second, so the check never found it and the file never opened. Now the permission is written down under both names.What Changed
Before: on a mapped or
substdrive, clicking a terminal file link shows the action popover, but Open file does nothing — no editor tab, no message. The read behind it fails withAccess denied: path resolves outside allowed directories.After: the file opens.
Mechanism.
authorizeExternalPath(src/main/ipc/filesystem-auth.ts) remembers the path as given and itsfs.realpathSyncresult. Every later read goes throughresolveAuthorizedPath, which canonicalizes with the nativefs.promises.realpath. For most paths the two agree. For a mapped orsubstdrive they don't: Node's defaultrealpathSyncis a JavaScript implementation that walks the path and keeps the drive letter, while the native call asks Windows for the final path and gets the UNC share or thesubsttarget. The grant now also remembersrealpathSync.native— the form the check computes — inside the existingtry, right after the JavaScript realpath.Why
authorizeExternalPath, so the fix applies to all of them without touching each read.tryon purpose. The grant runs synchronously on the main thread. If the JavaScript realpath has already failed — a missing file or an unreachable share — the native one would fail the same way, so it is not attempted, and a disconnected drive is not waited on twice.Alternatives considered:
normalizeExistingPath, the parent and missing-path checks), so this would move the mismatch rather than remove it.Linked Issue
Fixes #23018
Visual Proof
Windows 11, the e2e harness (
electron-headless, background launch), asubst Q:drive holdingdocs\page-1.png. The terminal printsQ:\docs\page-1.png; the link is clicked and Open file chosen.The popover, the same before and after:
main)Testing
New
src/main/ipc/filesystem-auth-external-grant-native-realpath.test.ts. It mocks only the two realpath calls to model a drive the native realpath resolves elsewhere, so it runs on every platform. Againstmain, the two grant cases fail with the issue's exact error; on this branch all four pass. The no-second-wait case fails if the native call is given its owntry, and the sibling case passes either way — it guards against widening.Real drive, no mocks (Windows 11).
authorizeExternalPaththenresolveAuthorizedPathon asubstdrive: a file grant and a folder grant are denied onmainand allowed here; an ungranted sibling is denied on both.Realpath forms (Windows 11). The new entry equals what the check computes for a
substdrive, 8.3 short names, lower-cased paths, WSL UNC paths (\\wsl.localhost\…,\\wsl$\…, where nothing changes) and missing files (both throw).End to end (Windows 11). The Visual Proof run above, from a temporary spec that is not part of this PR: on
mainnothing opens and no toast appears; herepage-1.pngopens in the editor.Regression. 89 test files — everything importing
filesystem-auth, everything undersrc/main/ipc/filesystem-*, and the tests of modules that callauthorizeExternalPath— run with and without the change. None fails because of it: 26 fail identically on unmodifiedmainon this Windows machine (13 file-watcher timing cases; the rest are symlink, Unicode-normalization, POSIX-path, WSLrgand OS-opened-markdown delivery cases). Two more failed only in the with-change batch; run alone, one fails the same way onmain, and the other — a real file-watcher test that never reachesfilesystem-auth— passed 5 of 5.CI static analysis, run locally. The PR Checks static-analysis steps that apply to this change, the two it routes in (
verify:localization-extraction,smoke:orcad-terminal), andpnpm typecheck— 25 in all: 23 pass. The other two fail the same way on unmodifiedmainon this machine.verify:skill-bundle-manifestcompares the CRLFresources/skills/*.jsonof acore.autocrlf=truecheckout byte for byte ([Bug]: pnpm lint cannot pass on a stock Windows checkout (CRLF line endings) #14479).smoke:orcad-terminalpasses its terminal round trip, then hitsEPERMdeleting its temp folder right after stopping the server — a Windows cleanup race in the script.Not tested: a real
net usenetwork share (asubstdrive reproduces the same JavaScript/native split); macOS and Linux by hand (CI runs the unit test).I manually tested these changes locally
Automated tests added/updated, or explained why not below
AI Disclosure
Written with Claude Opus 5.5 (Claude Code). I reproduced the bug on my Windows machine before the fix, reviewed the change, and ran every check above locally.
Review
The judgement call is where to fix it: at the grant (this PR) or at the check. I chose the grant because the native canonical form the check uses is what the rest of the module already compares against.
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
AI code review summary (Claude Opus 5.5):
substdrives, 8.3 short names, casing and WSL paths (see Testing).terminal-file-open-routing.ts: the relay or runtime is the security boundary). The change is confined to the local main-process grant. WSL\\wsl.localhost\…and\\wsl$\…paths give the same form both ways and are unaffected. Folder workspaces and git worktrees are not involved — the grant is per path.authorizeExternalPathis unchanged.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)🤖 Generated with Claude Code
https://claude.ai/code/session_01ADAPnmpPBMctKbwkj2i9NS