jdx/mise · error
remote cache path escapes its output root
Error message
remote cache path escapes its output root
What it means
Thrown by validate_cache_path when mise restores a task output from the HTTP remote cache. Every path that comes back from the remote store (the manifest's roots array and each joined child path in the directory tree) must be a plain relative path: absolute paths and any ParentDir, RootDir, or Windows drive-Prefix component are rejected. It is a path-traversal guard so a corrupt or hostile cache server cannot make mise write outside the restore directory.
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 9dcfcaa0dc)
Solutions
- Purge the affected entry (action result + blobs) from the remote cache and re-run the task so a trusted mise re-uploads it
- Verify every writer to the shared cache runs the same pinned mise version (CI included)
- If you operate the cache server, validate directory protos and manifests for relative, single-tree paths at upload time
- Reproduce with MISE_DEBUG=1 to capture the offending digest and inspect the blob it served
Example fix
// remote manifest roots (downloaded blob) // before "roots": ["/home/ci/build/out"] // after "roots": ["out"]
Defensive patterns
Strategy: validation
Validate before calling
use std::path::{Component, Path};
fn is_safe_cache_path(path: &Path) -> bool {
!path.as_os_str().is_empty()
&& !path.is_absolute()
&& !path.components().any(|c| matches!(
c,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
))
}
// before trusting a downloaded manifest:
for root in &manifest.roots {
assert!(is_safe_cache_path(Path::new(root)), "corrupt cache entry");
} Type guard
fn is_safe_cache_path(path: &std::path::Path) -> bool {
!path.as_os_str().is_empty()
&& !path.is_absolute()
&& !path.components().any(|c| matches!(
c,
std::path::Component::ParentDir
| std::path::Component::RootDir
| std::path::Component::Prefix(_)
))
} Try / catch
match restore_result {
Ok(entry) => entry,
Err(err) if err.to_string().contains("escapes its output root") => {
warn!("corrupt remote cache entry; purging and rebuilding");
purge_remote_entry(&key).await?;
run_uncached(&task).await?
}
Err(err) => return Err(err),
} Prevention
- Only let a single pinned mise version write to a shared remote cache
- Reject non-relative root paths at the cache server on upload
- Treat any traversal-family restore error as cache corruption: purge and rebuild, never ignore
- Onboard shared caches in CI with MISE_DEBUG=1 so bad blobs are identified early
When it happens
Trigger: HttpTaskCacheStore::get() downloads a manifest whose roots contain e.g. "/out" or "a/../../etc"; or materialize_remote_tree() joins a remote directory entry name with its parent and the result contains a ParentDir/RootDir/Prefix component. The bail happens before any file is written.
Common situations: Shared CAS/HTTP cache written by non-mise tooling or hand-edited manifests; a proxy or script rewriting JSON bodies; version skew where another mise release serialized roots differently; deliberate tampering tests against the cache server.
Related errors
- path must not escape the task directory
- remote cache symlink target must be relative
- remote cache symlink target escapes its output root
- content-level SLSA verification rejected unsafe archive path
- brew-cask: invalid {kind} '{value}'
AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17).
Data as JSON: /api/errors/6b8fef3ea2823426.
Report an issue: GitHub.