{"record":{"id":"7308d47dc363cee0","repo":"rustfs/rustfs","slug":"rename-destination-contains-an-invalid-path-compon","errorCode":null,"errorMessage":"rename destination contains an invalid path component","messagePattern":"rename destination contains an invalid path component","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/ecstore/src/disk/os.rs","lineNumber":3742,"sourceCode":"\n#[cfg(windows)]\nfn windows_rename_source_is_allowed(attributes: u32, reparse_tag: u32) -> bool {\n    use windows_sys::Win32::{Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT, System::SystemServices::IO_REPARSE_TAG_DEDUP};\n\n    attributes & FILE_ATTRIBUTE_REPARSE_POINT == 0 || reparse_tag == IO_REPARSE_TAG_DEDUP\n}\n\npub(crate) fn mkdir_all_below_existing_base_std(\n    dir_path: &Path,\n    base_dir: &Path,\n    publication_root: &PublicationRoot,\n) -> io::Result<ExistingBaseDirectoryGuard> {\n    let relative = dir_path\n        .strip_prefix(base_dir)\n        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, \"rename destination must remain below its base directory\"))?;\n    for component in relative.components() {\n        if !matches!(component, Component::Normal(_) | Component::CurDir) {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                \"rename destination contains an invalid path component\",\n            ));\n        }\n    }\n\n    #[cfg(unix)]\n    {\n        let _ = publication_root;\n        use rustix::fs::{Mode, OFlags, mkdirat, open, openat};\n        use rustix::io::Errno;\n\n        let flags = OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC;\n        let mode = Mode::RWXU | Mode::RWXG | Mode::RWXO;\n        let mut parents = vec![open(base_dir, flags, Mode::empty()).map_err(io::Error::from)?];\n\n        for component in relative.components() {\n            let Component::Normal(component) = component else {","sourceCodeStart":3724,"sourceCodeEnd":3760,"githubUrl":"https://github.com/rustfs/rustfs/blob/201c653dcd34c2a01b9aec5991ed76176b342118/crates/ecstore/src/disk/os.rs#L3724-L3760","documentation":"After stripping base_dir, the relative destination path contains a component that is not Normal or CurDir — typically ParentDir (`..`), or a root/prefix component leaking in. The guarded create only walks plain named components, so it rejects the path with InvalidInput before touching the filesystem.","triggerScenarios":"dir_path like <base>/../escape strips to ../escape; paths assembled from unnormalized user input containing dot-dot segments.","commonSituations":"User-supplied object or bucket names containing traversal segments after a buggy join; URL-decoded prefixes reintroducing dot-dot.","solutions":["Lexically clean the relative path (drop ParentDir components) before calling","Validate user input at the API boundary so traversal segments never reach the disk layer","Unit-test the path builder with dot-dot, dot, and empty components"],"exampleFix":"// before\nlet dir = base.join(format!(\"{}/../tmp\", rel));\n\n// after\nlet dir = base.join(rel).join(\"tmp\");","handlingStrategy":"validation","validationCode":"fn is_safe_relative(rel: &std::path::Path) -> bool {\n    rel.components()\n        .all(|c| matches!(c, std::path::Component::Normal(_) | std::path::Component::CurDir))\n}","typeGuard":null,"tryCatchPattern":"Err(err) if err.kind() == io::ErrorKind::InvalidInput && err.to_string().contains(\"invalid path component\") => reject the input as a traversal attempt; no retry","preventionTips":["Validate user-supplied names at the API boundary (no dot-dot, no separators)","Clean relative paths before joining onto the base"],"tags":["path-validation","path-traversal","invalid-input","mkdir"],"backgroundTag":"invalid-path-component","analyzedSha":"201c653dcd34c2a01b9aec5991ed76176b342118","analyzedAt":"2026-08-20T21:57:04.799Z","contentChangedAt":"2026-08-20T21:57:04.799Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}