jdx/mise · error

invalid path inside recovery directory

Error message

invalid path inside recovery directory

What it means

When validating a Dir snapshot, every recorded relative path must be non-empty, contain only Normal components (no `..`, `.`, root, or prefix), and be unique. This bail means the snapshot's internal path list is malformed — empty, traversal-like, or duplicated. It guards against restoring files outside the recovery directory from corrupt or crafted journal data.

Source

Thrown at src/system/history/recovery.rs:169

            read_blob(state_dir, content)?;
        }
        PathSnapshot::Dir {
            files, links, dirs, ..
        } => {
            let mut seen = BTreeSet::new();
            for relative in files
                .iter()
                .map(|f| &f.rel)
                .chain(links.iter().map(|f| &f.rel))
                .chain(dirs.iter().map(|f| &f.rel))
            {
                if relative.as_os_str().is_empty()
                    || !relative
                        .components()
                        .all(|c| matches!(c, Component::Normal(_)))
                    || !seen.insert(relative)
                {
                    bail!("invalid path inside recovery directory");
                }
            }
            for leaf in files
                .iter()
                .map(|f| &f.rel)
                .chain(links.iter().map(|f| &f.rel))
            {
                if seen
                    .iter()
                    .any(|other| *other != leaf && other.starts_with(leaf))
                {
                    bail!("recovery directory descends through a file or symlink");
                }
            }
            for file in files {
                read_blob(state_dir, &file.content)?;
            }
        }

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Inspect the pending-operation JSON for the offending `rel` entry and correct it to a clean relative path like `sub/file.txt`.
  2. Discard the corrupt record and accept current contents; restore files manually from your dotfiles repository.
  3. Report as a bug if the state directory was never edited — capture should never produce such paths.

Example fix

// before (malicious/corrupt entry)
"rel": "../../.ssh/authorized_keys"

// after (clean relative path)
"rel": "git/config"
Defensive patterns

Strategy: validation

Validate before calling

fn rel_is_safe(rel: &std::path::Path) -> bool {
    !rel.as_os_str().is_empty()
        && rel.components().all(|c| matches!(c, std::path::Component::Normal(_)))
}
// check every rel entry in the snapshot before recovery

Try / catch

match result { Err(e) if e.to_string().contains("invalid path inside recovery directory") => /* snapshot corrupt or tampered; discard record and restore manually */, other => other? }

Prevention

When it happens

Trigger: validate_snapshot -> PathSnapshot::Dir branch encounters a file/link/dir entry whose `rel` is empty, contains ParentDir/CurDir/root components, or duplicates an already-seen relative path.

Common situations: Hand-edited or corrupted pending-operation JSON, an injected/malicious journal, or a bug in snapshot capture producing duplicate or non-canonical 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


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/7245533563c3d2d7. Report an issue: GitHub.