Skip to content

fix(tools): detect Windows drive-letter paths as absolute on POSIX hosts (#54039)#54066

Open
eliot-onbox wants to merge 1 commit intoopenclaw:mainfrom
eliot-onbox:fix/windows-read-path-54039
Open

fix(tools): detect Windows drive-letter paths as absolute on POSIX hosts (#54039)#54066
eliot-onbox wants to merge 1 commit intoopenclaw:mainfrom
eliot-onbox:fix/windows-read-path-54039

Conversation

@eliot-onbox
Copy link
Copy Markdown

Problem

On POSIX hosts, path.isAbsolute("C:\\...") returns false, so when an agent passes a Windows absolute path to the read/write/edit tools, the workspace root gets prepended — producing doubled paths like:

C:\Users\Dan\.openclaw\workspace\:\Users\Dan\.openclaw\workspace\HEARTBEAT.md

Reported in #54039.

Fix

Add a WIN_DRIVE_LETTER_RE (/^[A-Za-z]:[/\\]/) fallback alongside path.isAbsolute() in three path resolution entry points:

  • sandbox-paths.ts: resolveToCwd, resolveAllowedTmpMediaPath
  • pi-tools.read.ts: resolveToolPathAgainstWorkspaceRoot

On native Windows this is redundant, but it protects against cross-platform edge cases.

Tests

6 new regression tests across both files (backslash paths, forward-slash paths, relative path passthrough).

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 24, 2026

Greptile Summary

This PR adds a WIN_DRIVE_LETTER_RE regex fallback (/^[A-Za-z]:[/\\]/) in sandbox-paths.ts and pi-tools.read.ts so that Windows-style absolute paths (e.g. C:\Users\Dan\...) are not incorrectly treated as relative on POSIX hosts, which was causing the workspace root to be prepended and producing doubled paths. The fix is minimal, well-scoped, and backed by 6 new regression tests covering both backslash and forward-slash Windows paths as well as passthrough of normal relative paths.

  • Two entry points patched: resolveToCwd / resolveAllowedTmpMediaPath in sandbox-paths.ts and resolveToolPathAgainstWorkspaceRoot in pi-tools.read.ts.
  • Security posture unchanged: In resolveAllowedTmpMediaPath, Windows paths that now pass the candidateIsAbsolute check still fail the subsequent isPathInside(openClawTmpDir, resolved) guard (on POSIX, path.resolve("C:\\...") resolves relative to process.cwd(), not to any sensitive directory).
  • Minor duplication: WIN_DRIVE_LETTER_RE is declared identically in both files rather than in a shared utility; this is acceptable for module decoupling but could drift over time.

Confidence Score: 5/5

  • This PR is safe to merge; it is a targeted cross-platform bug fix with adequate regression tests and no security regressions.
  • The change is minimal and well-isolated: three call sites receive an OR-condition guard, existing sandbox security checks (isPathInside, assertNoPathAliasEscape) continue to gate access, and all new tests pass. The regex is correctly formed and handles both separator styles.
  • No files require special attention.

Reviews (1): Last reviewed commit: "fix(tools): detect Windows drive-letter ..." | Re-trigger Greptile

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46f114b871

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

if (path.isAbsolute(expanded)) {
return expanded;
if (path.isAbsolute(expanded) || WIN_DRIVE_LETTER_RE.test(expanded)) {
return path.resolve(expanded);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return drive-letter paths without resolving via process cwd

On POSIX hosts, path.resolve("C:\\...") is not treated as a Windows absolute path; it gets prefixed with process.cwd(). Because this branch now classifies drive-letter inputs as absolute and then calls path.resolve(expanded), Windows-style paths are still rewritten to host-local paths (often still under the workspace when process.cwd() matches it), so the doubled-path behavior from #54039 can still occur in sandbox path resolution. This should preserve the original drive-letter string (or normalize with path.win32) instead of resolving with POSIX path.resolve.

Useful? React with 👍 / 👎.

const candidate = mapped.startsWith("@") ? mapped.slice(1) : mapped;
return path.isAbsolute(candidate)
return path.isAbsolute(candidate) || WIN_DRIVE_LETTER_RE.test(candidate)
? path.resolve(candidate)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid resolving tool drive-letter paths with POSIX path.resolve

This change has the same cross-platform issue in tool path guarding: on POSIX, path.resolve(candidate) for C:\\... or D:/... produces <process.cwd>/C:\\... rather than preserving the Windows absolute path. That means the new "absolute" handling still rewrites the input, and now depends on global process cwd instead of the provided workspace root, so the fix can misclassify or mismatch paths in memory-flush path checks.

Useful? React with 👍 / 👎.

…sts (openclaw#54039)

On POSIX, `path.isAbsolute('C:\\...')` returns false, causing the read/write/edit
tools to join a Windows absolute path with the workspace root — producing doubled
paths like `C:\workspace\C:\workspace\file.md`.

Add a WIN_DRIVE_LETTER_RE (/^[A-Za-z]:[/\\]/) fallback to the three path
resolution entry points:

- sandbox-paths.ts: resolveToCwd, resolveAllowedTmpMediaPath
- pi-tools.read.ts: resolveToolPathAgainstWorkspaceRoot

On native Windows this is redundant (path.isAbsolute already handles it), but it
protects against cross-platform edge cases.

Includes regression tests for both files.
@eliot-onbox eliot-onbox force-pushed the fix/windows-read-path-54039 branch from 46f114b to 4916a71 Compare March 27, 2026 18:25
@openclaw-barnacle openclaw-barnacle bot added docker Docker and sandbox tooling agents Agent runtime and tooling size: S labels Mar 27, 2026
@eliot-onbox
Copy link
Copy Markdown
Author

Addressed the review feedback from @chatgpt-codex-connector:

Problem: On POSIX, path.resolve("C:\\...") prepends process.cwd() to the Windows path, producing something like /home/user/C:\Users\... instead of preserving the original.

Fix: When WIN_DRIVE_LETTER_RE matches, return the candidate path unchanged instead of running it through path.resolve(). This preserves the Windows absolute path as-is on POSIX hosts. On native Windows, the path.isAbsolute branch still handles it correctly.

Tests now assert exact equality (toBe) instead of loose pattern matching, so any path mangling will fail the tests.

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4916a712a9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +413 to +416
if (WIN_DRIVE_LETTER_RE.test(candidate)) {
// On POSIX, path.resolve would prepend cwd to a Windows drive-letter path.
// Return the candidate unchanged — it is already absolute on the remote OS.
return candidate;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize drive-letter candidates before returning them

The new early return for WIN_DRIVE_LETTER_RE skips path.resolve on Windows too, so resolveToolPathAgainstWorkspaceRoot can now return non-canonical equivalents like C:/... or C:\dir\..\file. In wrapToolMemoryFlushAppendOnlyWrite, that value is compared by exact string against allowedAbsolutePath (built with path.resolve), so memory-flush writes can be rejected even when the agent targets the same file. This regression only appears on Windows-style absolute inputs, but it breaks a valid write path in memory-flush runs.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling docker Docker and sandbox tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant