Skip to content

[codex] Fix Windows allowedRoots bypass#40

Open
Ahmed-Hindy wants to merge 1 commit into
Waishnav:mainfrom
Ahmed-Hindy:codex/fix-windows-allowedroots-bypass
Open

[codex] Fix Windows allowedRoots bypass#40
Ahmed-Hindy wants to merge 1 commit into
Waishnav:mainfrom
Ahmed-Hindy:codex/fix-windows-allowedroots-bypass

Conversation

@Ahmed-Hindy

@Ahmed-Hindy Ahmed-Hindy commented Jun 25, 2026

Copy link
Copy Markdown

Summary

Fixes #13.

This PR closes a Windows-specific allowedRoots bypass where a path on a different drive from the configured allowlist root could be treated as allowed.

Root cause

isPathInsideRoot() uses path.relative(resolvedRoot, resolvedPath) to decide whether a requested path is contained by an allowed root. The existing check rejected relative paths that escaped the root with .. segments:

!relationship.startsWith("..") &&
relationship !== ".." &&
!relationship.includes(`..${sep}`)

That is enough for same-drive Windows paths and POSIX paths, but it misses an important Windows behavior: when path.relative() compares paths on different drives, it can return an absolute path instead of a ..-prefixed relative path.

For example:

path.relative(
  "G:\\Projects\\Dev\\Github\\devspace",
  "C:\\Users\\Administrator",
);

// "C:\\Users\\Administrator"

Because "C:\\Users\\Administrator" does not start with .., the old predicate could incorrectly accept it as inside the allowed root.

Security impact

This undermines the filesystem allowlist boundary on Windows. A server configured with a narrow allowlist such as:

G:\Projects\Dev\Github\devspace

could still allow MCP clients to open a workspace under a different drive, such as:

C:\Users\Administrator

That matches the class of behavior reported in #13 and can allow reads/writes outside the configured approved roots.

Changes

  • Import isAbsolute from node:path.
  • Reject absolute path.relative() results inside isPathInsideRoot().
  • Add a Windows-only regression test that verifies C:\Users\Administrator is rejected when the only allowed root is on G:\....

Why this fix

A valid contained path should produce either:

  • an empty relationship, when the path is the root itself; or
  • a non-absolute relative path that does not escape through ...

So the containment check now requires the relationship to be non-absolute before accepting it:

!isAbsolute(relationship)

This preserves the existing same-drive behavior while closing the cross-drive Windows bypass.

Validation

Automated checks run on Windows:

npx tsx src/roots.test.ts
npm run typecheck
npm test
npm run build

All passed.

Manual QA was also performed on Windows through ChatGPT / DevSpace Local5 with the fixed build running.

DevSpace was restarted with this configured allowed root:

G:\Projects\Dev\Github\devspace-test

Confirmed through ChatGPT / DevSpace Local5 that the configured root is accessible:

G:\Projects\Dev\Github\devspace-test

Confirmed that the previous DevSpace source folder is no longer accessible after restart:

G:\Projects\Dev\Github\devspace-1.0.2

Confirmed that unrelated paths on other drives are rejected as outside allowed roots:

C:\Users\Ahmed Hindy\nuke-aov-rebuilder
F:\Users\Ahmed Hindy\Documents\houdiniTools\houdini_asset_relinker

Also confirmed broader parent/sibling probes are blocked, so ChatGPT cannot discover access by scanning upward outside the configured root.

Manual QA result:

  • configured root opens successfully
  • previous/sibling project root is blocked
  • unrelated C:\... and F:\... paths are blocked
  • broader parent paths are blocked
  • Windows cross-drive allowlist bypass appears fixed

Additional observation: DevSpace does not currently expose a tool that lists configured allowed roots directly to the MCP client. ChatGPT can only verify access by attempting to open a specific absolute path.

Summary by CodeRabbit

  • Bug Fixes
    • Improved allowed-path checking to better handle Windows and other edge cases where path comparisons can be misleading.
    • Tightened path validation so paths outside the permitted roots are more reliably rejected.
    • Added a Windows-specific test to verify invalid home directory paths are blocked with the expected error message.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fda3e4d2-516b-4a50-a7b5-905c7f6dbeab

📥 Commits

Reviewing files that changed from the base of the PR and between 65be252 and 5c16dd4.

📒 Files selected for processing (2)
  • src/roots.test.ts
  • src/roots.ts

📝 Walkthrough

Walkthrough

Allowed-root validation now rejects absolute results from path.relative(), and the test suite adds a Windows-only regression for a disallowed C:\Users\Administrator path.

Changes

Windows allowed-root validation

Layer / File(s) Summary
Root guard update
src/roots.ts
isPathInsideRoot() imports isAbsolute and treats absolute relationship results as outside the allowed root before the traversal checks.
Windows regression test
src/roots.test.ts
A Windows-only test asserts that assertAllowedPath throws /Path is outside allowed roots/ for an uncovered C:\Users\Administrator path.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I hopped by roots with twitchy nose,
and found a path where caution grows.
No sneaky Windows trail slips through,
the burrow keeps its boundaries true.
🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing a Windows allowedRoots bypass.
Linked Issues check ✅ Passed The changes address the Windows allowedRoots bypass and add a regression test for blocked access outside the configured root.
Out of Scope Changes check ✅ Passed The PR only changes path validation logic and its Windows regression test, which are directly in scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@Ahmed-Hindy Ahmed-Hindy marked this pull request as ready for review June 27, 2026 11:28
@Ahmed-Hindy

Copy link
Copy Markdown
Author

Potential Issues
PR #40 fixes the Windows cross-drive bug, but a few things could still go wrong:

  1. Junctions or symlinks can still escape the allowed folder

Example:

Allowed root:
G:\Projects\SafeFolder

Inside it:
G:\Projects\SafeFolder\escape

But escape is a junction pointing to:
C:\Users\Ahmed\Secrets

DevSpace may think this is allowed because the typed path starts inside SafeFolder, even though Windows resolves it somewhere else.

This is the most important remaining risk.

  1. Some valid Windows path forms may be rejected

Example:

C:\repo\file.txt
\\?\C:\repo\file.txt

These can refer to the same place, but the current check may treat them as different and reject the second one.

That is safer than allowing too much, but it may annoy users with unusual Windows path formats.

  1. Mapped drives and network shares may behave unexpectedly

Example:

Z:\project
\\server\share\project

These might point to the same folder, but the current check may not know that unless it resolves the real filesystem path.

Likely result: false rejection, not a security bypass.

  1. Test coverage is still narrow

The PR only adds one Windows regression test:

G:\allowed-root
C:\outside-root

That proves the reported bug is fixed, but it does not cover junctions, symlinks, network paths, mapped drives, or weird Windows path formats.

  1. Some clients may lose access they previously had

If someone was accidentally relying on the old broken behavior, this PR will block them.

Example:

Allowed root:
G:\Projects

Previously accepted by mistake:
C:\Users\Ahmed

That is a good break, because the old behavior was unsafe, but it is still a behavior change.

Future PR
I would keep PR #40 as the small direct fix, then open a separate hardening PR.

That future PR should:

  1. Add real filesystem canonicalization

Use fs.realpath() or similar so DevSpace compares where paths actually point, not just what the string looks like.

  1. Block symlink and junction escapes

If a path inside an allowed root points outside through a junction/symlink, reject it.

  1. Handle new files carefully

For reads, the file exists, so realpath() is easy.

For writes, the file may not exist yet. In that case, resolve the nearest existing parent folder and make sure that parent is still inside the allowed root.

  1. Add more Windows tests

Add tests for:

junction inside allowed root pointing outside
symlink inside allowed root pointing outside
same root path
child path
sibling path
C:\ vs G:\ cross-drive path
UNC paths like \\server\share
mapped-drive style paths
\\?\C:\... namespaced paths
mixed-case paths
trailing slash paths
  1. Document the expected behavior

The security docs should say that allowed roots are resolved to their real filesystem locations, and that symlinks/junctions cannot be used to escape them.

So: PR #40 is good and fixes the reported bug. The next PR should be a broader “canonical path containment” security hardening pass.

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.

'allowedRoots' have some problems

1 participant