{"record":{"id":"0b349b84eed475c4","repo":"jdx/mise","slug":"remote-cache-path-must-be-relative","errorCode":null,"errorMessage":"remote cache path must be relative","messagePattern":"remote cache path must be relative","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache_store.rs","lineNumber":503,"sourceCode":"                action: action_digest,\n                metadata: Some(metadata),\n                output_root,\n                version: 1,\n            })\n            .await\n    }\n\n    async fn remove(&self, _key: &str) -> Result<()> {\n        // Ordinary cache writers intentionally have no remote-delete authority.\n        Ok(())\n    }\n\n    fn touch(&self, _key: &str) {}\n}\n\nfn validate_cache_path(path: &Path) -> Result<()> {\n    if path.as_os_str().is_empty() || path.is_absolute() {\n        bail!(\"remote cache path must be relative\");\n    }\n    if path.components().any(|component| {\n        matches!(\n            component,\n            Component::ParentDir | Component::RootDir | Component::Prefix(_)\n        )\n    }) {\n        bail!(\"remote cache path escapes its output root\");\n    }\n    Ok(())\n}\n\nfn cache_name(path: &Path) -> Result<String> {\n    let name = path\n        .file_name()\n        .and_then(|name| name.to_str())\n        .ok_or_else(|| eyre!(\"remote cache paths must be valid UTF-8\"))?;\n    if name.is_empty() || name == \".\" || name == \"..\" || name.contains(['/', '\\0']) {","sourceCodeStart":485,"sourceCodeEnd":521,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/task/task_cache_store.rs#L485-L521","documentation":"validate_cache_path rejects cache paths that are empty or absolute. Remote cache entry paths (manifest roots, archive entries, tree nodes) must be relative so they can be safely joined under a local output directory. Absolute paths would escape the artifact directory when materialized.","triggerScenarios":"get() validating manifest roots, archive_to_cas() validating tar entry paths, or materialize_remote_tree() validating node paths when a path is empty or starts with '/' (or a Windows prefix) (task_cache_store.rs:503).","commonSituations":"A hand-crafted or third-party-written cache archive containing absolute paths (e.g. tar entries like /home/user/out); manifests edited manually; archives created with different tar tooling including absolute members.","solutions":["Regenerate the cache archive/manifest so all paths are relative to the output root (strip leading '/' and mount prefixes).","Re-run the task to republish a clean entry rather than reusing the offending archive.","If building archives yourself, use relative paths: tar -C <outdir> . instead of tar <abs-path>.","Reject/inspect the remote entry at ingestion before it enters the cache."],"exampleFix":"// before (creating archive)\n$ tar -czf out.tar.zst /home/me/project/dist\n// after\n$ cd /home/me/project && tar -czf out.tar.zst dist","handlingStrategy":"validation","validationCode":"fn is_safe_relative(p: &Path) -> bool {\n    !p.as_os_str().is_empty() && !p.is_absolute()\n}","typeGuard":"fn is_valid_cache_path(p: &std::path::Path) -> bool {\n    use std::path::Component::*;\n    !p.as_os_str().is_empty()\n        && !p.is_absolute()\n        && p.components().all(|c| matches!(c, Normal(_)))\n}","tryCatchPattern":"match validate_cache_path(&entry_path) {\n    Ok(()) => {},\n    Err(e) if e.to_string().contains(\"must be relative\") => {\n        return Err(eyre!(\"cache archive {key} has absolute path {entry_path:?}; rebuild it\"));\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Build archives with tar -C <outdir> . so members are relative.","Never hand-edit cache manifests or archives.","Sanitize paths (strip leading '/') before uploading to the cache.","Test archives on one platform before sharing cross-platform."],"tags":["cache","path","validation","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}