Skip to content

fix(toolset): accept a Windows tool path spelled with backslashes - #11937

Merged
jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix/windows-tool-path-separators
Aug 13, 2026
Merged

jdx merged 1 commit into
jdx:mainfrom
JamBalaya56562:fix/windows-tool-path-separators

Conversation

@JamBalaya56562

@JamBalaya56562 JamBalaya56562 commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Closes #11192.

tool.<name>.path rejects every path spelled the way Windows spells it.

The problem

Measured on 2026.8.2 windows-x64, same directory each time:

path = 'C:\Users\Jam\…\tools\bin'   ->  mise ERROR invalid version for node in …\mise.toml
                                        mise ERROR invalid tool path "C:\\Users\\…": contains
                                                   forbidden character '\'
                                        exit=1

path = 'C:/Users/Jam/…/tools/bin'   ->  exit=0, node  path:C:/Users/Jam/…/tools/bin

path = '~/.local/bin'               ->  exit=0

So the only spelling that works is the one nothing on Windows produces — Explorer, pwd, and
tab-completion all give backslashes. The error says a character is forbidden, not that slashes get
through, so there is nothing to act on.

The existing unit test puts "C:/Users/foo" on the accepted side. C:\Users\foo is on neither
side: the standard Windows spelling was never decided, it just fell through to the reject arm.

Correcting the analysis on the thread

There is a comment on #11192 saying validation runs after ~ expansion, so any path: using ~
is unusable. That is not true of current main — validate_path_string runs on the raw string
(tool_request.rs:110) and resolve_path expands afterwards, which is why the third line above
passes. The reporter was on 2026.7.11; the order changed since.

Why the character is on the list

#9814 — "security: reject shell metacharacters in version
strings and CI inputs"
— added it. A path: value resolves into ctx.rootPath for path-mode
tools, and vfox plugin hooks interpolate that into shell commands, so the fix killed that RCE class
at the Rust boundary rather than in each Lua plugin. \ is on the list because it is an escape in
a POSIX shell.

That reasoning holds. It just does not describe Windows, where \ is the path separator.

The change

Rewrite \ to / before validating, on Windows only. Not "allow \ there".

Win32 accepts / wherever it accepts \, so the meaning of the path is unchanged — and no
backslash reaches ctx.rootPath
, so the property #9814 established is kept exactly rather than
weakened. Dropping \ from the list on Windows would have been the smaller diff and the worse
answer.

Some(("path", p)) => {
    let p = windows_path_separators(p);
    validate_path_string(&p)?;
    let path = resolve_path(&p, &source);

Unix is untouched: the function is a borrow-through no-op there, because \ is a legal character
in a filename — rewriting would change which file is meant — and it really is a shell escape.

resolve_path needed nothing: ~/ matching and is_absolute() both work on the rewritten form
(C:/Users/foo is absolute).

One code path covers both spellings of the config: uv = { path = "…" } is folded into the string
path:… in mise_toml.rs before it reaches ToolRequest::new.

\\?\ and \\.\ are deliberately not rewritten

Extended-length and device prefixes are the one place Windows does not accept /, so rewriting
would hand back a path that looks right and does not resolve — silently. They are left alone, which
means they keep being rejected exactly as they are today; nothing regresses and nothing new
silently breaks. The error for them now says what is actually wrong instead of naming a character
the user cannot avoid.

I have not verified against a real \\?\ path that forward slashes break it — that is from
Microsoft's documentation. The design does not depend on the claim: not rewriting is the
conservative branch either way.

Not widened here

The list is POSIX-shaped in the other direction too: & and % are cmd's real metacharacters and
path: accepts both today. Flagging it rather than fixing it — that is a separate question about
what the list should be on Windows, and this PR is about a spelling that has no alternative.

Tests

path: had no e2e coverage at all, on either platform. There were only unit tests for the
validator.

  • unit — the rewrite-then-validate pair in the order the path: arm runs it. Windows: the
    native spellings are accepted and come back reduced (C:\Users\foo\bin → C:/Users/foo/bin,
    and C:\Program Files\tool, and ~\.local\bin); \\?\C:\… and \\.\COM1 are unchanged and
    still rejected. Unix: /tmp/\rm is unchanged and still rejected — the control for "the rewrite
    did not leak off Windows". Every platform: quotes, backtick and $ still rejected, which is the
    control for "the rewrite did not widen anything but the separator". These live in the file's
    existing test module, which is not #[cfg(unix)], so CI runs them on Windows too.
  • e2e-win (new) — tool_path.Tests.ps1. Writes a native C:\… path into mise.toml and
    asserts mise ls --current succeeds and reports the rewritten path, so it checks the
    resolved value rather than only that the command survived. Both TOML spellings that deliver a
    backslash — a literal string and a doubled basic string — plus the forward-slash spelling, which
    has to keep working, and a quote in the path, which has to keep failing. \\?\C:\Users\foo and
    \\.\COM1 assert the message, not just the failure: only the no-rewrite branch produces
    extended-length and device paths …, so a change that started normalising them would fall
    through to the generic contains forbidden character arm — still a failure and still non-zero,
    which a test checking only those two things would accept.
  • e2e (new, unix) — config/test_tool_path_validation: \ and $(id) still rejected, with an
    ordinary path as the control so the two rejections are about the character rather than about
    path: being broken in the sandbox.

The TOML quoting matters, and review caught that I had skipped it

docs/configuration.md's path: entry showed only a Homebrew example, so it gains the Windows
case. The first version of that sentence said path:C:\tools\node works without saying which TOML
quoting actually delivers that string, which is a trap. Measured:

node = { path = "C:\tools\node" }     ->  mise WARN  semver range "path:C:<TAB>ools<LF>ode" is not
                                                     supported for node …          exit=1
node = { path = "C:\Users\foo" }      ->  Error loading settings file: TOML parse error
                                          (\U is TOML's unicode escape)
node = { path = 'C:\tools\node' }     ->  reaches mise intact
node = { path = "C:\\tools\\node" }   ->  reaches mise intact

The first is the bad one: not an error. TOML reads \t as a tab, the value silently becomes
something else, and what the user sees is a complaint about a semver range with no mention of a
path. The docs now name the two spellings that work and say what the third does. The e2e-win suite
covers both of them rather than only the literal-string form the test happened to use.

docs/configuration.md is hand-written, so nothing is regenerated.

Not touched: the semver range … wording that the mangled string produces. mise is reporting
accurately on a string TOML already broke; how unparseable tool versions are reported is a separate
question.

Not run

cargo fmt --all only; no local build. The three measurements at the top are of a released binary
reproducing the bug, not of the fix — CI is what exercises it, and windows-e2e is the job that
matters.

Summary by CodeRabbit

  • Bug Fixes

    • Windows path: tool requests now support backslash and forward-slash separators with consistent normalization.
    • Unsafe paths, including quotes, carriage returns, command substitution, and unsupported Windows prefixes, remain rejected.
    • Unix behavior is unchanged, including rejection of backslash separators.
  • Documentation

    • Clarified Windows path separators, normalization, and TOML escaping, including tab interpretation.
  • Tests

    • Added cross-platform end-to-end coverage for valid and invalid tool paths.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: d1fb2c86-823e-4644-a6ec-51e0f47cfcec

📥 Commits

Reviewing files that changed from the base of the PR and between 1e9d7e2 and 07288fd.

📒 Files selected for processing (1)
  • docs/configuration.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/configuration.md

📝 Walkthrough

Walkthrough

path: requests now normalize Windows backslashes to forward slashes before validation and resolution. Special Windows prefixes remain rejected. Tests cover Windows, Unix, shell metacharacters, quote handling, and TOML escaping. Documentation describes the normalized path form.

Changes

Tool-path normalization

Layer / File(s) Summary
Path normalization and validation
src/toolset/tool_request.rs
Windows separators are rewritten before validation. Extended-length and device paths remain rejected. Unix backslashes remain rejected. Unit tests cover these cases.
End-to-end coverage and documentation
e2e-win/tool_path.Tests.ps1, e2e/config/test_tool_path_validation, docs/configuration.md
End-to-end tests cover accepted Windows path forms, invalid characters, special Windows prefixes, and normal relative paths. The documentation describes TOML escaping and forward-slash normalization.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Mergeability Score: ⚪ Minimal · up to 07288

This change normalizes Windows tool paths before validation while preserving Unix behavior and adds focused tests and documentation. No actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant ToolPathRequest
  participant PathNormalizer
  participant PathValidator
  participant PathResolver
  ToolPathRequest->>PathNormalizer: Rewrite Windows backslashes
  PathNormalizer->>PathValidator: Validate normalized path
  PathValidator->>PathResolver: Resolve accepted path
Loading

Possibly related PRs

  • jdx/mise#11312: Both PRs address Windows path-separator normalization in different code paths.
  • jdx/mise#11947: Both PRs modify Windows path: validation and cover shell metacharacter rejection.

Poem

A rabbit hops from slash to slash,
Windows paths now parse with less clash.
Unsafe paths remain denied,
Tests check each route side by side.
Clean forward paths are verified.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: accepting Windows tool paths that use backslashes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/configuration.md`:
- Around line 395-396: Update the Homebrew/Windows path documentation to explain
TOML quoting for native Windows paths: recommend forward slashes or show a
literal-string mise.toml example using single quotes so backslashes are not
interpreted as escapes.

In `@e2e-win/tool_path.Tests.ps1`:
- Around line 17-24: Update the suite’s BeforeAll setup to save the original
process-scoped MISE_TRUSTED_CONFIG_PATHS value, then modify AfterAll to restore
that saved value when present and remove the environment variable only when the
saved value is $null, while preserving the existing test-directory cleanup.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: cea4eebf-424d-407f-9050-ef4a6837dbd8

📥 Commits

Reviewing files that changed from the base of the PR and between 7eecbec and fcabc97.

📒 Files selected for processing (4)
  • docs/configuration.md
  • e2e-win/tool_path.Tests.ps1
  • e2e/config/test_tool_path_validation
  • src/toolset/tool_request.rs

Comment thread docs/configuration.md Resolved
Comment thread e2e-win/tool_path.Tests.ps1 Resolved
@JamBalaya56562
JamBalaya56562 force-pushed the fix/windows-tool-path-separators branch from fcabc97 to e9a4375 Compare August 12, 2026 16:55

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
e2e-win/tool_path.Tests.ps1 (1)

81-93: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the protected Windows prefixes.

Lines 81-93 verify an ordinary forbidden character but do not exercise \\?\ and \\.\. Add TOML literal-string cases for both prefixes and assert that mise ls --current fails. windows_path_separators has a dedicated no-rewrite branch for these prefixes. This suite will not detect regressions that normalize them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e-win/tool_path.Tests.ps1` around lines 81 - 93, Add TOML literal-string
test cases in the `tool_path.Tests.ps1` path-validation tests for both protected
Windows prefixes, `\\?\` and `\\.\`, using `mise ls --current` and asserting a
nonzero exit status. Ensure the cases exercise the `windows_path_separators`
no-rewrite branch and preserve the existing invalid-path coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@e2e-win/tool_path.Tests.ps1`:
- Around line 81-93: Add TOML literal-string test cases in the
`tool_path.Tests.ps1` path-validation tests for both protected Windows prefixes,
`\\?\` and `\\.\`, using `mise ls --current` and asserting a nonzero exit
status. Ensure the cases exercise the `windows_path_separators` no-rewrite
branch and preserve the existing invalid-path coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: c8d1e0f1-9584-4e92-9576-85f14f49c545

📥 Commits

Reviewing files that changed from the base of the PR and between fcabc97 and e9a4375.

📒 Files selected for processing (2)
  • docs/configuration.md
  • e2e-win/tool_path.Tests.ps1
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/configuration.md

@JamBalaya56562
JamBalaya56562 force-pushed the fix/windows-tool-path-separators branch from e9a4375 to 9b9b350 Compare August 12, 2026 16:59
@JamBalaya56562

Copy link
Copy Markdown
Contributor Author

Cover the protected Windows prefixes. … windows_path_separators has a dedicated no-rewrite branch for these prefixes. This suite will not detect regressions that normalize them.

Added in 9b9b350d1ee5, though the premise needs one correction: a regression that normalised them was already detected, by
test_extended_length_tool_path_is_left_alone_and_rejected, which asserts both halves —

assert_eq!(super::windows_path_separators(input), input, "{input:?} should not be rewritten");
assert!(accept_tool_path(input).is_err(), "{input:?}");

— and lives in a module that is not #[cfg(unix)], so CI runs it on Windows. It is this suite that did not cover them, which is not the same as the branch being uncovered.

The e2e cases are still worth having, for something the unit test cannot see: they go through the whole chain — mise.toml → TOML → mise_toml.rs folding the table into path:… → ToolRequest::new — so a future layer that canonicalised separators before validation runs would break them while the unit test kept passing.

Both assert the message rather than only the failure:

$out | Should -BeLike '*extended-length and device paths*'

That is the wording only the no-rewrite branch produces. If something started rewriting these, they would fall through to the generic contains forbidden character arm — still a failure, still a non-zero exit, and a test that only checked those two things would pass.

Written as two plain It blocks rather than -ForEach: nothing in e2e-win/ uses -ForEach today, and I cannot run Pester locally to check I have the data-binding right, so this is not the change to introduce it in.

@JamBalaya56562
JamBalaya56562 force-pushed the fix/windows-tool-path-separators branch from 9b9b350 to 1e9d7e2 Compare August 12, 2026 23:48
@github-actions

Copy link
Copy Markdown
Contributor

This PR currently has failing checks. If this continues for 7 days, it will be closed automatically.

This is warning day 1 of 7.

Please update the PR when you have a chance. Feel free to reopen or create a new PR if it is closed and you'd like to continue working on it.

This comment was generated by an automated workflow.

`tool.<name>.path` rejected every native Windows path:

    invalid tool path "C:\Users\me\tools\bin": contains forbidden character '\'

so anything copied out of Explorer or printed by `pwd` was unusable, and only
the forward-slash spelling worked. The existing tests put "C:/Users/foo" on the
accepted side; `C:\Users\foo` was on neither, and the error does not hint that
slashes get through.

The list `path:` values are checked against was added by jdx#9814 to keep shell
syntax out of `ctx.rootPath`, which vfox plugin hooks build commands with. It
is written for a POSIX shell, where `\` is an escape. On Windows it is the path
separator.

Rather than drop `\` from the list there -- which would weaken what jdx#9814
established -- the separator is rewritten to `/` before the check. Win32 accepts
`/` wherever it accepts `\`, so the meaning is unchanged, and no backslash
reaches a plugin hook. Unix is untouched: `\` is a legal filename character
there and really is a shell escape, so it stays rejected.

Extended-length and device prefixes (`\?\`, `\.\`) are left alone, since those
are the one place Windows does not accept `/`; rewriting them would produce a
path that looks right and does not resolve. They keep being rejected, with a
message that now says why.

`path:` had no e2e coverage on either platform before this.

Closes jdx#11192
@JamBalaya56562
JamBalaya56562 force-pushed the fix/windows-tool-path-separators branch from 6708f23 to 07288fd Compare August 13, 2026 01:00
@JamBalaya56562
JamBalaya56562 marked this pull request as ready for review August 13, 2026 18:14
@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR normalizes ordinary Windows backslashes before validating and resolving path: tool requests, while retaining rejection of extended-length/device paths and other shell metacharacters.

  • Adds platform-specific separator normalization and validation tests.
  • Adds Windows and Unix end-to-end coverage for accepted and rejected tool paths.
  • Documents Windows path separators and correct TOML quoting.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete changed-code failure identified.

The normalization is Windows-only, runs before the existing security validation, preserves explicitly unsupported namespace prefixes for rejection, and is covered by platform-specific unit and end-to-end tests.

Important Files Changed

Filename Overview
src/toolset/tool_request.rs Normalizes ordinary Windows path separators before existing validation, preserves non-Windows behavior, and adds focused platform-specific unit coverage.
e2e-win/tool_path.Tests.ps1 Exercises literal, escaped, forward-slash, unsafe, extended-length, and device path forms through the Windows Pester harness.
e2e/config/test_tool_path_validation Confirms Unix backslashes and command substitution remain rejected while an ordinary relative path succeeds.
docs/configuration.md Documents Windows separator normalization and distinguishes valid TOML literal/doubled-backslash forms from interpreted basic strings.

Reviews (1): Last reviewed commit: "fix(toolset): accept a Windows tool path..." | Re-trigger Greptile

@jdx
jdx merged commit 12d4996 into jdx:main Aug 13, 2026
31 checks passed
@JamBalaya56562
JamBalaya56562 deleted the fix/windows-tool-path-separators branch August 13, 2026 18:21
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.

2 participants