jdx/mise · error
invalid recovery destination
Error message
invalid recovery destination
What it means
validate_destination guards the recovery path passed to recover_path. The library refuses any destination that is not an absolute path or whose components include a `..` (ParentDir) component, because recovery writes pre-snapshot content back to a fixed location and relative/parent-relative paths could redirect writes anywhere on disk. This is a deliberate security hardening against path traversal, not an incidental failure.
Solutions
- Convert the destination to an absolute canonical path first (e.g. `std::path::absolute()` or `fs::canonicalize`) and strip any `..` components before calling recover_path
- Reject or normalize the path at the source (config parse, CLI arg validation) so `..` never reaches the API
- Ensure the caller passes a true absolute path (starting with `/`) not a path relative to cwd
Example fix
// before
recover_path(Path::new("../data/state"), prior)?;
// after
let dest = std::path::absolute("../data/state")?; // still fails if it keeps `..`
let dest = dunce::canonicalize(dest)?; // resolve/remove `..` components
recover_path(&dest, prior)?; Defensive patterns
Strategy: validation
Validate before calling
fn is_safe_dest(p: &std::path::Path) -> bool {
use std::path::Component;
p.is_absolute() && !p.components().any(|c| matches!(c, Component::ParentDir))
}
if !is_safe_dest(&dest) { /* reject before calling recover_path */ } Type guard
fn safe_abs_path(p: &std::path::Path) -> Option<std::path::PathBuf> {
use std::path::Component;
let abs = std::path::absolute(p).ok()?;
match abs.components().any(|c| matches!(c, Component::ParentDir)) {
true => None,
false => Some(abs),
}
} Prevention
- Always canonicalize/absolutize user-supplied paths before recovery calls
- Validate CLI path arguments at the boundary and reject `..` early
- Add a unit test asserting recover_path rejects relative and `..`-containing paths
When it happens
Trigger: Calling recover_path (directly or via a history recovery command) with a relative path like `foo/bar`, a path built with `..` such as `/a/../etc/x`, or a path containing `..` anywhere in its components even if it normalizes away.
Common situations: Passing a user-supplied or CLI-relative path without canonicalizing it first; constructing destinations with `Path::new(base).join(rel)` where rel came from config and contains `..`; scripts run from different working directories producing relative paths.
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
- remote cache path must be relative
- bootstrap_command returned an unsafe mise path
- brew-cask: command_wrapper requires a command name without…
- brew-cask: invalid
- brew-cask: invalid structured flight path
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/08573704413e1db9.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/history/recovery.rs:111
after,
PathState::Dir {
identity: Some(_),
..
}
))
{
bail!("directory contents cannot be verified safely; left untouched");
}
validate_snapshot(state_dir, prior)?;
if PathState::observe(path) != *after {
bail!("changed while preparing recovery; left untouched");
}
restore(state_dir, path, prior)
}
fn validate_destination(path: &Path) -> Result<()> {
if !path.is_absolute() || path.components().any(|c| matches!(c, Component::ParentDir)) {
bail!("invalid recovery destination");
}
for parent in path.ancestors().skip(1) {
if std::fs::symlink_metadata(parent).is_ok_and(|meta| meta.is_symlink()) {
bail!("a parent directory is now a symlink; left untouched");
}
}
Ok(())
}
fn validate_blob_id(hash: &str) -> Result<()> {
if hash.len() != 64 || !hash.bytes().all(|b| b.is_ascii_hexdigit()) {
bail!("invalid recovery content identifier");
}
Ok(())
}
pub(super) fn read_blob(state_dir: &Path, blob: &Blob) -> Result<Vec<u8>> {
use base64::Engine;View on GitHub (pinned to 533346cc37)