Skip to content

fix(runtime): normalize Windows workspace paths - #3286

Open
SulimanAbdulrazzaq wants to merge 3 commits into
ultraworkers:mainfrom
SulimanAbdulrazzaq:fix/3278-windows-workspace-path
Open

SulimanAbdulrazzaq wants to merge 3 commits into
ultraworkers:mainfrom
SulimanAbdulrazzaq:fix/3278-windows-workspace-path

Conversation

@SulimanAbdulrazzaq

@SulimanAbdulrazzaq SulimanAbdulrazzaq commented Aug 9, 2026 •

Copy link
Copy Markdown

Summary

  • Normalize Windows extended-length (\\?\) and regular path representations before enforcing workspace boundaries.
  • When the lexical comparison fails, compare the path's canonical form before reporting an escape, so other Windows spellings of a workspace path (different letter case, such as a working directory typed as c:\users\..., or the \\.\ device namespace) are no longer reported as escaping. A path whose canonical form is outside the workspace stays rejected.
  • Cover DOS and UNC prefix variants, and the canonical fallback, with regression tests.

Fixes #3278

Anti-slop triage

Verification

  • cargo fmt --manifest-path rust/Cargo.toml --all -- --check
  • git diff --check
  • cargo +stable-x86_64-pc-windows-gnu check --manifest-path rust/Cargo.toml --target x86_64-pc-windows-gnu -p runtime (at 9e7fd60)
  • cargo +stable-x86_64-pc-windows-gnu clippy --manifest-path rust/Cargo.toml --target x86_64-pc-windows-gnu -p runtime --lib (at 9e7fd60; completed with existing warnings outside this change)
  • Linux (GitHub Codespace, rustc 1.98.1) at dc34649: cargo test -p runtime --no-fail-fast passes (lib 613 passed, integration 2 and 12 passed). The new accepts_a_path_that_resolves_inside_the_workspace fails with the canonical fallback removed and passes with it; a path that resolves outside the workspace stays rejected.
  • cargo fmt --all --check clean; cargo clippy -p runtime --all-targets passes with the existing warnings, none in file_ops.rs.
  • Windows 11 (rustc 1.97.1, x86_64-pc-windows-gnu), a standalone harness using the exact normalize_for_comparison, validate_workspace_boundary and resolves_within from dc34649: a lower-cased spelling and a \\.\ device-namespace spelling of a workspace path are accepted (the previous head rejected both), a \\?\ spelling is accepted, and the workspace's parent is rejected.
  • Runtime unit tests on Windows: the repository's Windows-target test build currently fails in pre-existing Unix-only std::os::unix::fs::PermissionsExt tests, so the #[cfg(windows)] tests (accepts_equivalent_extended_path_at_workspace_boundary, accepts_other_windows_spellings_of_a_workspace_path) have not run inside the crate. The harness above covers the same cases.
  • No secrets, tokens, private logs, or unrelated generated files are included.

Resolution gate

Keep extended-length and regular Windows paths comparable when enforcing workspace boundaries, including first-run roots that cannot yet be canonicalized. Add regression coverage for DOS and UNC path forms.

Signed-off-by: Suliman Abdulrazzaq <suliman9000a@gmail.com>
@1716775457damn

Copy link
Copy Markdown

Nice catch on the \\?\ prefix. One edge case worth confirming: does this also handle \\.\ device namespace paths (e.g. \\.\C:\foo)? Those can appear when calling GetFullPathName without the FILE_FLAG_OPEN_REPARSE_POINT flag on symlinks/junctions. Also, short (8.3) name normalization via GetLongPathName may be relevant for paths coming from legacy tooling.

@1716775457damn

Copy link
Copy Markdown

Confirmed - I've hit this on Windows too: some APIs hand back paths with the \?\ prefix while the workspace root is stored without it, so the boundary check falsely reports escapes. Normalizing both forms before comparison is the right approach, and covering DOS/UNC variants in regression tests is good. One thing worth checking: Windows path comparisons should also be case-insensitive - make sure the normalized comparison doesn't regress on case differences (e.g. C:\Users vs c:\users).

accepts_equivalent_extended_path_at_workspace_boundary builds its paths
with backslash separators. Backslashes only separate path components on
Windows, so on Linux each path is a single component, the boundary check
rejects the path, and cargo test fails.

Run the test only on Windows, and call validate_workspace_boundary through
super:: so non-Windows test builds do not warn about an unused import.
@1716775457damn

Copy link
Copy Markdown

Thanks for the follow-up commit dbf5389 - gating the extended-path boundary test to Windows makes sense so it doesn't run on non-Windows runners where the prefix handling differs. Two quick checks from my earlier review: does the normalization also cover the \.\ device namespace prefix, and is the final boundary comparison case-insensitive (C:\Users vs c:\users)? If both are handled, this is good to merge for #3278.

@1716775457damn

Copy link
Copy Markdown

Reviewing the diff in file_ops.rs — dbf5389 的 normalize_for_comparison 已覆盖 \\?\UNC\ 与 \\?\ 两种前缀并带回归测试,方向正确。但对照我上一条评论的两个点:1) \\.\ device namespace 前缀(如 \\.\C:\foo)仍未在 normalize 中处理,Path::canonicalize 在 symlink/junction 场景确实可能返回这种形式;2) 边界比较仍直接走 Path::starts_with,对大小写差异(C:\Users vs c:\users)不敏感这一点没体现——Windows 文件系统大小写不敏感,建议比较前统一 lowercase(或加一个 case-insensitive 的 starts_with 辅助)。这两点补上后我认为就可以合并修复 #3278 了。

validate_workspace_boundary compared paths lexically, so a location inside
the workspace spelled with different letter case (a working directory typed
as c:\users\... for C:\Users\...) or through the \\.\ device namespace was
reported as escaping the workspace.

When the lexical comparison fails, compare the path's canonical form before
reporting an escape. A path whose canonical form is outside the workspace
stays rejected.
@SulimanAbdulrazzaq

Copy link
Copy Markdown
Author

@1716775457damn Both points were real, so dc34649 handles them. I checked on Windows 11 first: with the previous head, a lower-cased spelling of a workspace path (for example a working directory typed as c:\users\...) and a \\.\ device-namespace spelling were both reported as escaping the workspace.

validate_workspace_boundary now falls back to the path's canonical form when the lexical comparison fails, and accepts the path only if that canonical form is inside the workspace. That covers letter case, \\.\ and 8.3 short names without lowercasing anything (NTFS directories can be case-sensitive), and a path that resolves outside the workspace stays rejected.

  • Windows 11, a harness with the exact functions from the commit: the lower-cased and \\.\ spellings are now accepted (both were rejected before), and the workspace's parent is still rejected.
  • Linux: the new accepts_a_path_that_resolves_inside_the_workspace fails with the fallback removed and passes with it. The runtime crate passes 613 + 2 + 12 tests, and fmt and clippy are clean.

accepts_other_windows_spellings_of_a_workspace_path covers the same cases as a Windows test. Like the existing Windows test, it can't run inside the crate yet, because the Windows test build fails on the pre-existing Unix-only tests.

@1716775457damn

Copy link
Copy Markdown

dc34649 的 canonical fallback 方案确认可行:Path::canonicalize 在 Windows 上会同时解析 \?\ 前缀、\.\ device namespace 并统一大小写,正好覆盖我之前提的两个 case。从描述看 fallback 只在 lexical 比较失败时才触发,hot path 开销可控;canonicalize 对不存在的路径会失败,此时保持拒绝也符合预期。整体来看 #3278 的修复已完整,支持合并。

@1716775457damn

Copy link
Copy Markdown

Agreed — the canonical fallback in dc34649 covers both the \.\ device-namespace and the case-difference cases, and the Windows 11 verification matches exactly what I flagged earlier. I'll approve the 2 pending workflows so CI can run; once it comes back green I'll merge this to close #3278.

@1716775457damn

Copy link
Copy Markdown

Status update: Checks still shows 0 on dc34649 and both workflows remain "awaiting approval" — the earlier approval didn't trigger the runs. I'll re-approve from the Actions tab; once CI comes back green I'll merge this to close #3278. Ping me if any run log needs triaging.

@1716775457damn

Copy link
Copy Markdown

Status update after checking Actions/Checks just now: on head dc34649 the Rust #650 / Rust CI #1318 runs still show "Action required", and the run pages say "This workflow is awaiting approval from a maintainer in #3286". My account doesn't have an "Approve and run" button on either the run page or the PR Checks tab, so fork-branch workflows appear to require approval from a repo maintainer with write access. @SulimanAbdulrazzaq or any maintainer — could you click "Approve and run" on the pending runs? Once CI comes back green I'll merge this to close #3278; happy to triage any run log in the meantime.

@1716775457damn

Copy link
Copy Markdown

跟进确认:canonical fallback 方案在 dc34649 上覆盖了大小写、\?\ 与 \.\ 前缀,修复完整。当前唯一阻塞仍是 fork 分支 workflow 的 maintainer 批准(#650 / #1318 显示 Action required)。@SulimanAbdulrazzaq 或其他有写权限的 maintainer 批准后 CI 跑绿,我即合并关闭 #3278。

@SulimanAbdulrazzaq

Copy link
Copy Markdown
Author

Head is still dc34649 (unchanged since 2026-09-18, mergeable against main). It covers both points raised here: normalize_for_comparison strips the \\?\ and \\?\UNC\ prefixes, and validate_workspace_boundary falls back to the path's canonical form when the lexical comparison fails — that is what accepts the \\.\ device namespace, letter-case differences and 8.3 short names. A path whose canonical form is outside the workspace is still rejected.

Rust #650 and Rust CI #1318 are both in action_required. On a pull request from a fork, only someone with write access to this repository can start those runs — I am the fork-side author, so I cannot. @code-yeongyu, could you start them when you get a chance?

The branch is one commit behind main (the AGENTS.md docs commit) and still merges cleanly. I can rebase onto current main first if you would prefer the runs to start from there.

@1716775457damn

Copy link
Copy Markdown

Agreed — dc34649 is still the head and merges cleanly against main. The canonical-fallback approach covers both the \?\ and \.\ prefix variants plus letter-case/8.3 differences, so #3278 is fully addressed from the code side. The only remaining blocker is maintainer approval of the fork-branch workflows (Rust #650 / Rust CI #1318). Happy to triage run logs once @code-yeongyu or another maintainer starts them — good to merge once CI comes back green.

@1716775457damn

Copy link
Copy Markdown

Review 结论确认:dc34649 的 canonical-fallback 方案已完整覆盖 \?\ 与 \.\ 前缀、大小写及 8.3 短名差异,修复方向正确,代码侧可以收尾。当前唯一阻塞仍是 fork 分支 workflow 的 maintainer 批准(Rust #650 / Rust CI #1318)。一旦 CI 跑绿我即合并关闭 #3278;期间有任何 run log 需要排查随时 ping 我。

@1716775457damn

Copy link
Copy Markdown

+1 on the canonical-fallback approach — it cleanly covers the \?\ and \.\ prefixes plus case/8.3 short-name differences without weakening the containment check. Code side looks ready; the only remaining gate is the fork workflow approval (Rust #650 / CI #1318). Happy to triage logs once runs start, then merge and close #3278.

@SulimanAbdulrazzaq

Copy link
Copy Markdown
Author

@code-yeongyu could you approve the pending workflow runs on this one when you have a moment? It fixes #3278: the workspace boundary check reports a Windows path as escaping the workspace when it is spelled with a \\?\ prefix, in a different letter case, or through the \\.\ namespace. Rust and Rust CI are waiting for maintainer approval, and the branch has no conflicts.

@1716775457damn

Copy link
Copy Markdown

Thanks for the ping. Verified on my side: dc34649's canonical fallback covers the \?\ / \?\UNC\ / \.\ prefix spellings plus letter-case and 8.3 short-name differences, without loosening the containment check — paths that resolve outside the workspace are still rejected. Rust #650 and Rust CI #1318 remain in action_required awaiting maintainer approval; once they run green I'll merge this and close #3278.

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.

grep_search: spurious "escapes workspace boundary" on first run in a directory with no .claw/ (Windows)

2 participants