jdx/mise · critical
created path component {} was replaced before it could be op
Error message
created path component {} was replaced before it could be opened What it means
A race-condition check after creating a directory component with an exclusive open: the library fstats the freshly created fd and verifies it is owned by the current effective uid. If it is not, and this process created the component (created_by_us), some other actor replaced the path between creation and open. This detects symlink-swap attacks or concurrent interference on freshly created components.
Source
Thrown at src/system/managed_files.rs:1667
return Err(error).wrap_err_with(|| {
format!(
"failed to create path component {}",
component_path.display()
)
});
}
};
let created = openat(&directory, name.as_os_str(), flags, Mode::empty())
.wrap_err_with(|| {
format!(
"failed to open newly available path component {} without following symlinks",
component_path.display()
)
})?;
let stat = nix::sys::stat::fstat(&created)?;
if stat.st_uid != nix::unistd::geteuid().as_raw() {
if created_by_us {
bail!(
"created path component {} was replaced before it could be opened",
component_path.display()
);
} else {
bail!(
"path component {} was concurrently created by another user",
component_path.display()
);
}
}
created
}
};
current.push(name);
}
Ok(directory)
}
View on GitHub (pinned to afd2eddd3a)
Solutions
- Ensure the managed prefix directory is only writable by the current user/root (chmod/chown the parent)
- Re-run the operation; transient races usually succeed once the path is settled
- Remove the path component that was tampered with and recreate it as the correct user
- Avoid sharing managed directories between users; give each user its own prefix
Example fix
// before $ ls -ld /opt/tools # drwxrwxrwx root root // after $ sudo chown root:root /opt/tools && sudo chmod 755 /opt/tools
Defensive patterns
Strategy: retry
Validate before calling
fn prefix_is_private(path: &Path) -> std::io::Result<bool> {
use std::os::unix::fs::MetadataExt;
let mut cur = path.to_path_buf();
while let Some(parent) = cur.parent() {
let m = std::fs::metadata(parent)?;
if m.mode() & 0o022 != 0 { return Ok(false); }
cur = parent.to_path_buf();
}
Ok(true)
} Type guard
fn component_owned_by_euid(path: &Path) -> bool {
use std::os::unix::fs::MetadataExt;
std::fs::symlink_metadata(path)
.map(|m| m.uid() == unsafe { libc::geteuid() })
.unwrap_or(false)
} Try / catch
for attempt in 0..3 {
match result {
Err(e) if e.to_string().contains("was replaced before it could be opened") && attempt < 2 => continue,
Err(e) => return Err(e),
Ok(v) => return Ok(v),
}
} Prevention
- Restrict managed prefixes to a single trusted user
- Avoid concurrent installs by different users into the same prefix
- Re-run the operation after transient races
- Remove and recreate tampered components as the correct user
When it happens
Trigger: During open_or_create_directory_tree, a component was just created (O_EXCL-style) but the fd's st_uid differs from the process euid — meaning the path was replaced (e.g. by a symlink plant) between mkdir and open. Without created_by_us, a different message ('concurrently created by another user') is used instead.
Common situations: Local attacker racing a privileged mise operation in a shared directory; parallel package managers or build systems creating the same path concurrently as different users; container/image build steps running as mixed uids over the same prefix.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- expected world-writable ancestor to be refused
- expected pre-planted symlink destination to be refused
- refusing to follow symlink {} from an untrusted parent direc
- brew-cask: refusing elevated operation because target appear
- brew-cask: refusing operation through untrusted directory {}
AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09).
Data as JSON: /api/errors/c98e067e03ffbb17.
Report an issue: GitHub.