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
- Do not materialize the entry; purge that cache key/blob from the remote cache.
- Re-run the task with --force to repopulate the entry from a trusted local execution.
- Audit the remote cache endpoint/credentials — this pattern often indicates a compromised or misconfigured cache.
- 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
- Never archive trees containing '..' members; normalize paths before packaging.
- Only consume cache entries from trusted cache servers over authenticated transports.
- Purge, don't reuse, any entry flagged with traversal components.
- Audit remote cache credentials — traversal entries often indicate compromise.
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
- brew-cask: invalid
- brew-cask: invalid structured flight path
- invalid tool ref : contains path-traversal sequence
- invalid tool version
- remote cache path must be relative
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)