jdx/mise · error

remote cache path escapes its output root

Error message

remote cache path escapes its output root

What it means

validate_cache_path rejects paths containing '..', root, or Windows prefix components because such paths can escape the artifact output directory during materialization. The store treats this as a hostile or corrupt cache entry and refuses it.

Solutions

  1. Do not materialize the entry; purge that cache key/blob from the remote cache.
  2. Re-run the task with --force to repopulate the entry from a trusted local execution.
  3. Audit the remote cache endpoint/credentials — this pattern often indicates a compromised or misconfigured cache.
  4. If the archive is self-produced, rebuild it so no member contains '..' or absolute components.

Example fix

// before: archive contains '..' entries
$ tar -czf out.tar.zst ../dist
// after
$ cd project && tar -czf out.tar.zst ./dist
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Component;
fn contains_escape(p: &std::path::Path) -> bool {
    p.components().any(|c| matches!(c, Component::ParentDir | Component::RootDir | Component::Prefix(_)))
}

Type guard

fn is_contained(p: &std::path::Path) -> bool {
    use std::path::Component::*;
    p.components().all(|c| matches!(c, Normal(_) | CurDir))
}

Try / catch

if let Err(e) = validate_cache_path(&p) {
    if e.to_string().contains("escapes its output root") {
        // treat as hostile entry: skip and purge
        return Ok(SkipReason::UnsafePath);
    }
    return Err(e);
}

Prevention

When it happens

Trigger: get(), archive_to_cas(), or materialize_remote_tree() encounter a cache path with a ParentDir ('..'), RootDir, or Prefix component (task_cache_store.rs:511), e.g. a tar entry like ../../etc/passwd or C:\ in the manifest roots.

Common situations: Malicious or compromised remote cache server serving traversal paths; archives built with '../' members; manually edited manifests; cross-platform archives carrying Windows drive prefixes.

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@533346cc37 (2026-09-17). Data as JSON: /api/errors/6b8fef3ea2823426. Report an issue: GitHub.

Appendix: source

Thrown at src/task/task_cache_store.rs:511

    async fn remove(&self, _key: &str) -> Result<()> {
        // Ordinary cache writers intentionally have no remote-delete authority.
        Ok(())
    }

    fn touch(&self, _key: &str) {}
}

fn validate_cache_path(path: &Path) -> Result<()> {
    if path.as_os_str().is_empty() || path.is_absolute() {
        bail!("remote cache path must be relative");
    }
    if path.components().any(|component| {
        matches!(
            component,
            Component::ParentDir | Component::RootDir | Component::Prefix(_)
        )
    }) {
        bail!("remote cache path escapes its output root");
    }
    Ok(())
}

fn cache_name(path: &Path) -> Result<String> {
    let name = path
        .file_name()
        .and_then(|name| name.to_str())
        .ok_or_else(|| eyre!("remote cache paths must be valid UTF-8"))?;
    if name.is_empty() || name == "." || name == ".." || name.contains(['/', '\0']) {
        bail!("invalid remote cache path component");
    }
    Ok(name.to_string())
}

fn validate_cache_name(name: &str) -> Result<()> {
    let path = Path::new(name);
    if name.is_empty()

View on GitHub (pinned to 533346cc37)