{"record":{"id":"2c03875a8bbf2654","repo":"jdx/mise","slug":"invalid-remote-cache-path-component","errorCode":null,"errorMessage":"invalid remote cache path component","messagePattern":"invalid remote cache path component","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache_store.rs","lineNumber":522,"sourceCode":"    }\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']) {\n        bail!(\"invalid remote cache path component\");\n    }\n    Ok(name.to_string())\n}\n\nfn validate_cache_name(name: &str) -> Result<()> {\n    let path = Path::new(name);\n    if name.is_empty()\n        || name == \".\"\n        || name == \"..\"\n        || name.contains(['/', '\\\\', '\\0'])\n        || path.components().count() != 1\n        || !matches!(path.components().next(), Some(Component::Normal(_)))\n    {\n        bail!(\"invalid remote cache path component\");\n    }\n    Ok(())\n}\n","sourceCodeStart":504,"sourceCodeEnd":540,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/task/task_cache_store.rs#L504-L540","documentation":"cache_name extracts the final file name of a cache path for use as a directory entry name and rejects empty names, '.', '..' and names containing '/' or NUL. The path must resolve to a single usable component for building the local materialized tree.","triggerScenarios":"build_directory() calls cache_name() on a tree node path whose file_name is empty, non-UTF-8-adjacent segment like '.', '..', or containing '/' or a NUL byte (task_cache_store.rs:522).","commonSituations":"Corrupt remote tree metadata produced by a buggy uploader; paths ending in '/' handled as directory nodes with empty names; malicious entries embedding separators; binaries/non-UTF-8 names written by non-mise clients.","solutions":["Re-fetch or republish the remote tree entry; the stored node metadata is invalid.","Re-run the task to regenerate a clean output tree in the remote cache.","Check the uploader code for paths that keep trailing separators or embed multiple components.","Purge the offending cache key from the remote cache server."],"exampleFix":"// before: node path with trailing slash passed to cache_name\nlet name = cache_name(Path::new(\"out/dir/\"))?;\n// after: normalize before building nodes\nlet name = cache_name(Path::new(\"out/dir\"))?;","handlingStrategy":"validation","validationCode":"fn name_ok(p: &std::path::Path) -> bool {\n    match p.file_name().and_then(|n| n.to_str()) {\n        Some(n) => !(n.is_empty() || n == \".\" || n == \"..\" || n.contains(['/', '\\0'])),\n        None => false,\n    }\n}","typeGuard":"fn is_single_name(p: &std::path::Path) -> bool {\n    p.file_name().map(|n| n != std::ffi::OsStr::new(\"\") && n != \".\" && n != \"..\").unwrap_or(false)\n}","tryCatchPattern":"let name = cache_name(&node_path)\n    .map_err(|e| eyre!(\"remote tree node {node_path:?} unusable ({e}); republish entry\"))?;","preventionTips":["Normalize node paths (trim trailing '/') before uploading tree metadata.","Use UTF-8, single-segment names for tree nodes.","Reject NUL bytes and separators when generating node names.","Validate entries at upload time, not only download time."],"tags":["cache","path","validation"],"backgroundTag":"invalid-identifier-format","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"}