{"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":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/ecstore/src/disk/os.rs","lineNumber":2666,"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":2648,"sourceCodeEnd":2684,"githubUrl":"https://github.com/rustfs/rustfs/blob/9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de/crates/ecstore/src/disk/os.rs#L2648-L2684","documentation":"After the base-prefix check succeeds, mkdir_all_below_existing_base_std walks the remaining components and accepts only Normal and CurDir ('.') components. A ParentDir ('..') or RootDir component in the relative remainder is rejected with ErrorKind::InvalidInput, blocking destinations that would climb out of the base directory during recursive directory creation. It complements the strip_prefix check by validating the shape of the relative path, not just its prefix.","triggerScenarios":"A rename destination parent containing '..' (e.g. base/bucket/../../escape) or an embedded root component, so a component other than Normal/CurDir appears after prefix stripping.","commonSituations":"Object keys or bucket names with '..' segments that were not sanitized upstream, migration tools copying trees that contain '..' entries, or hand-built test paths.","solutions":["Sanitize at the API boundary: reject '..' segments (and leading '/') in bucket and object names before they reach the disk layer","Fix the specific caller emitting the path; this error marks an upstream validation gap","Keep the S3-layer key-validation tests as the regression guard for this class"],"exampleFix":"// before\nlet object = \"a/../../escape\"; // reaches disk layer -> invalid path component\n\n// after\nfn is_safe_key(k: &str) -> bool {\n    !k.split('/').any(|seg| seg == \"..\" || seg.is_empty() && false) && !k.starts_with('/')\n}\nassert!(is_safe_key(object), \"object key rejected\");","handlingStrategy":"validation","validationCode":"fn has_parent_dir_component(p: &Path) -> bool {\n    p.components().any(|c| matches!(c, std::path::Component::ParentDir))\n}\nif has_parent_dir_component(dst_parent) { return Err(/* invalid key */); }","typeGuard":"fn is_safe_object_key(key: &str) -> bool {\n    !key.starts_with('/') && key.split('/').all(|seg| seg != \"..\" && !seg.is_empty() || seg.is_empty())\n}\n// enforce at the S3 API boundary","tryCatchPattern":"Match ErrorKind::InvalidInput with the 'invalid path component' message; reject the request as a malformed key and audit how it passed upstream validation.","preventionTips":["Validate bucket/object names (no '..', no leading '/', no empty segments) at the trust boundary","Keep the S3-layer key-validation tests green as the regression guard for this class"],"tags":["path-validation","rename","security","traversal","invalid-input"],"backgroundTag":"path-traversal-guard","analyzedSha":"9e6e02ea09c86bedf44c7bd64a74ea02a0cff1de","analyzedAt":"2026-08-16T20:34:17.560Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}