jdx/mise · error
invalid tool ref {s:?}: contains path-traversal sequence
Error message
invalid tool ref {s:?}: contains path-traversal sequence What it means
validate_ref_string rejects ref values containing '..' to block path-traversal-style input that could escape intended directories or be interpreted as a range. It is raised during ToolRequest construction (new_with_options) for `ref:`/`branch:`/`tag:`/`rev:` values.
Source
Thrown at src/toolset/tool_request.rs:652
bail!("invalid tool version {s:?}: contains forbidden character {c:?}");
}
Ok(())
}
/// Validate `ref:`/`branch:`/`tag:`/`rev:` values. Same character rules as
/// version strings: branch/tag names already use the same broad vocabulary
/// (`/`, `+`, `-`, etc.), so only shell-quote-breaking characters and leading
/// dashes need rejection. Kept as a separate function for distinct error
/// messages.
fn validate_ref_string(s: &str) -> Result<()> {
if s.is_empty() {
return Ok(());
}
if s.starts_with('-') {
bail!("invalid tool ref {s:?}: must not start with '-'");
}
if s.contains("..") {
bail!("invalid tool ref {s:?}: contains path-traversal sequence");
}
if let Some(c) = s.chars().find(|c| is_forbidden_version_char(*c)) {
bail!("invalid tool ref {s:?}: contains forbidden character {c:?}");
}
Ok(())
}
/// Validate `path:` values. Filesystem paths legitimately contain `/`, spaces,
/// and many other characters, but the resolved path becomes `ctx.rootPath` /
/// `installPath` for path-mode tools and is interpolated into shell commands
/// by some plugin hooks. Reject the same shell-quote-breaking characters as
/// version strings — `$`, backtick, quotes, and `\` — so a hostile `path:`
/// entry in a project config cannot inject shell syntax. Path traversal is
/// intentionally not rejected here because `path:../tools/foo` is a normal
/// relative-path use case.
///
/// The list is written for a POSIX shell, which is why `\` is on it. On Windows `\` is a path
/// separator instead, so it is rewritten by [`windows_path_separators`] before it gets here ratherView on GitHub (pinned to afd2eddd3a)
Solutions
- Replace range syntax with the concrete ref you want, e.g. `ref:feature` instead of `ref:main..feature`.
- Use a specific commit via `rev:<sha>` if you need a precise revision.
- Sanitize generated ref strings (strip/validate '..') before writing them into config.
Example fix
// before
"github:owner/repo" = { branch = "main..dev" }
// after
"github:owner/repo" = { branch = "dev" } Defensive patterns
Strategy: validation
Validate before calling
function hasNoTraversal(ref) { return typeof ref === 'string' && !ref.includes('..'); }
if (!hasNoTraversal(ref)) throw new Error(`range syntax not allowed in ref: ${ref}`); Type guard
function isSingleRef(v) { return typeof v === 'string' && !v.includes('..'); } Prevention
- Remember git range syntax (a..b) is not a ref — resolve it to a concrete commit first.
- Strip '..' from any externally supplied ref strings before writing config.
When it happens
Trigger: A ref string like `node@ref:../../somewhere`, `branch = "a..b"` (a git range syntax), or any ref containing a double dot anywhere.
Common situations: Users pasting git range expressions (`main..feature`) where a single ref was expected, or attempting traversal via a ref field in mise.toml/.tool-versions.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- brew-cask: invalid {kind} '{value}'
- {option}: '{name}' must be a plain file name (no path separa
- {option}: '{path}' must be a safe relative path (no absolute
- invalid relay path
- brew-cask: refusing generic artifact copy outside Homebrew p
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/4369fcb759977369.
Report an issue: GitHub.