rustfs/rustfs · error · io::Error
rename destination contains an invalid path component
Error message
rename destination contains an invalid path component
What it means
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.
Source
Thrown at crates/ecstore/src/disk/os.rs:2666
#[cfg(windows)]
fn windows_rename_source_is_allowed(attributes: u32, reparse_tag: u32) -> bool {
use windows_sys::Win32::{Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT, System::SystemServices::IO_REPARSE_TAG_DEDUP};
attributes & FILE_ATTRIBUTE_REPARSE_POINT == 0 || reparse_tag == IO_REPARSE_TAG_DEDUP
}
pub(crate) fn mkdir_all_below_existing_base_std(
dir_path: &Path,
base_dir: &Path,
publication_root: &PublicationRoot,
) -> io::Result<ExistingBaseDirectoryGuard> {
let relative = dir_path
.strip_prefix(base_dir)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "rename destination must remain below its base directory"))?;
for component in relative.components() {
if !matches!(component, Component::Normal(_) | Component::CurDir) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"rename destination contains an invalid path component",
));
}
}
#[cfg(unix)]
{
let _ = publication_root;
use rustix::fs::{Mode, OFlags, mkdirat, open, openat};
use rustix::io::Errno;
let flags = OFlags::RDONLY | OFlags::DIRECTORY | OFlags::NOFOLLOW | OFlags::CLOEXEC;
let mode = Mode::RWXU | Mode::RWXG | Mode::RWXO;
let mut parents = vec![open(base_dir, flags, Mode::empty()).map_err(io::Error::from)?];
for component in relative.components() {
let Component::Normal(component) = component else {View on GitHub (pinned to 9e6e02ea09)
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
Example fix
// before
let object = "a/../../escape"; // reaches disk layer -> invalid path component
// after
fn is_safe_key(k: &str) -> bool {
!k.split('/').any(|seg| seg == ".." || seg.is_empty() && false) && !k.starts_with('/')
}
assert!(is_safe_key(object), "object key rejected"); Defensive patterns
Strategy: validation
Validate before calling
fn has_parent_dir_component(p: &Path) -> bool {
p.components().any(|c| matches!(c, std::path::Component::ParentDir))
}
if has_parent_dir_component(dst_parent) { return Err(/* invalid key */); } Type guard
fn is_safe_object_key(key: &str) -> bool {
!key.starts_with('/') && key.split('/').all(|seg| seg != ".." && !seg.is_empty() || seg.is_empty())
}
// enforce at the S3 API boundary Try / catch
Match ErrorKind::InvalidInput with the 'invalid path component' message; reject the request as a malformed key and audit how it passed upstream validation.
Prevention
- 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
When it happens
Trigger: 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.
Common situations: Object keys or bucket names with '..' segments that were not sanitized upstream, migration tools copying trees that contain '..' entries, or hand-built test paths.
Related errors
- rename destination must remain below its base directory
- rename base directory contains an invalid path component
- rename source must have a file name
- rename destination parent must have a name
- InvalidInput
AI-assisted analysis of rustfs/rustfs@9e6e02ea09 (2026-08-16).
Data as JSON: /api/errors/7308d47dc363cee0.
Report an issue: GitHub.