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

  1. Remove ".." from the path by canonicalizing first: use std::fs::canonicalize and verify the result, or build absolute component-clean paths.
  2. If the path is user-supplied, reject or normalize it before passing it to the API (the error is intentional traversal protection).
  3. Construct paths with PathBuf::join from trusted components rather than string concatenation.
  4. 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

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


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/e6221463829421e6. Report an issue: GitHub.