astrid-runtime/astrid · error
private directory contains traversal: {}
Error message
private directory contains traversal: {} What it means
unix_directory_walk resolves a path component-by-component with openat(..., O_NOFOLLOW) so symlinks and traversal cannot redirect private paths. It rejects any path containing ".." components or Windows-style Prefix components up front, throwing this error, because parent-directory references would let the resolved location escape the intended directory.
Source
Thrown at crates/astrid-core/src/platform_fs.rs:708
#[cfg(unix)]
fn open_directory_no_follow_unix(path: &Path) -> io::Result<std::fs::File> {
let (directory, _) = unix_directory_walk(path)?;
Ok(directory)
}
#[cfg(unix)]
fn unix_directory_walk(path: &Path) -> io::Result<(std::fs::File, Vec<std::ffi::OsString>)> {
use nix::errno::Errno;
use nix::fcntl::{OFlag, openat};
use nix::sys::stat::Mode;
use std::path::Component;
let components = path.components().collect::<Vec<_>>();
if components
.iter()
.any(|component| matches!(component, Component::ParentDir | Component::Prefix(_)))
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("private directory contains traversal: {}", path.display()),
));
}
let absolute = normalize_unix_system_alias(if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir()?.join(path)
});
let mut directory = if absolute
.components()
.next()
.is_some_and(|component| matches!(component, Component::RootDir))
{
std::fs::File::open("/")
} else {
return Err(io::Error::new(View on GitHub (pinned to affd8760f4)
Solutions
- Remove ".." from the path by canonicalizing first: use std::fs::canonicalize and verify the result, or build absolute component-clean paths.
- If the path is user-supplied, reject or normalize it before passing it to the API (the error is intentional traversal protection).
- Construct paths with PathBuf::join from trusted components rather than string concatenation.
- If you need an equivalent location, compute it without parent references (e.g. expand to the absolute path yourself).
Example fix
// before
ensure_private_directory(Path::new("/home/me/../me/.astrid"))?;
// after
let clean = std::fs::canonicalize("/home/me")?.join(".astrid");
ensure_private_directory(&clean)?; Defensive patterns
Strategy: validation
Validate before calling
fn is_traversal_free(path: &std::path::Path) -> bool {
use std::path::Component;
path.components().all(|c| !matches!(c, Component::ParentDir | Component::Prefix(_)))
} Type guard
fn safe_path(path: &std::path::Path) -> Option<std::path::PathBuf> {
use std::path::Component;
if path.components().any(|c| matches!(c, Component::ParentDir | Component::Prefix(_))) {
None
} else { Some(path.to_path_buf()) }
} Try / catch
match ensure_private_directory(path) {
Err(e) if e.kind() == std::io::ErrorKind::InvalidInput && e.to_string().contains("traversal") => {
eprintln!("rejecting traversal path: {e}");
},
other => other?,
} Prevention
- Canonicalize or normalize user-supplied paths before use
- Reject ".." segments at input boundaries
- Build paths with PathBuf::join, not string concatenation
When it happens
Trigger: Calling ensure_private_directory, validate_private_directory, or any private-file API with a path containing ".." (e.g. "/home/me/../me/.astrid"), or a path with a Windows prefix component.
Common situations: Untrusted or user-supplied paths containing ".."; path templates that splice in relative fragments; code that concatenates user input into a base directory without normalization.
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
- durable capsule {id} has unsafe WIT metadata path {relative}
- mountpoint contains traversal: {}
- capsule projection escaped its root: {}
- workspace tree contains a redirected or special entry: {}
- workspace descendant must be a non-empty relative path witho
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/e6221463829421e6.
Report an issue: GitHub.