astrid-runtime/astrid · error

named-pipe endpoint path must not contain a parent component

Error message

named-pipe endpoint path must not contain a parent component

What it means

pipe_name converts a user-supplied endpoint path into a per-user kernel pipe name (\\.\pipe\...). A `..` component would let the resulting pipe name escape the intended per-user namespace, so the library rejects any path containing a ParentDir component with InvalidInput before absolutizing it.

Source

Thrown at crates/astrid-core/src/local_transport/windows.rs:576

    options
        .first_pipe_instance(first)
        .reject_remote_clients(true);

    // SAFETY: `security.attributes` and its LocalAlloc-owned descriptor remain
    // valid for the complete CreateNamedPipeW call. Tokio does not retain the
    // pointer after `create_with_security_attributes_raw` returns.
    unsafe {
        options
            .create_with_security_attributes_raw(pipe_name, (&raw mut security.attributes).cast())
    }
}

fn pipe_name(path: &Path) -> io::Result<OsString> {
    if path
        .components()
        .any(|component| matches!(component, std::path::Component::ParentDir))
    {
        return Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "named-pipe endpoint path must not contain a parent component",
        ));
    }
    let absolute = std::path::absolute(path)?;
    let sid = current_user_sid()?;
    let endpoint = blake3::hash(absolute.as_os_str().as_encoded_bytes());
    let digest = blake3::hash(sid.as_bytes());
    Ok(OsString::from(format!(
        "{PIPE_PREFIX}{}{}",
        &digest.to_hex()[..24],
        &endpoint.to_hex()[..40]
    )))
}

#[cfg(feature = "test-support")]
pub(super) fn endpoint_name_for_test(path: &Path) -> io::Result<OsString> {
    pipe_name(path)

View on GitHub (pinned to affd8760f4)

Solutions

  1. Normalize the path before passing it: use std::path::absolute() (or Path::components-based cleanup) so no ParentDir component remains.
  2. Build endpoint paths from a fixed base with PathBuf::join on simple names only.
  3. Reject or canonicalize user/config-supplied endpoint paths at config-load time.

Example fix

// before
let ep = Path::new(&format!("{}\\..\\svc", base));
transport.bind(ep)?;
// after
let ep = std::path::absolute(Path::new(&base))?.join("svc");
transport.bind(&ep)?;
Defensive patterns

Strategy: validation

Validate before calling

fn endpoint_path_ok(p: &Path) -> bool {
    !p.components().any(|c| matches!(c, std::path::Component::ParentDir))
}
// call before bind/connect:
assert!(endpoint_path_ok(&cfg.endpoint));

Type guard

fn safe_endpoint(p: &Path) -> Option<PathBuf> {
    if p.components().any(|c| matches!(c, std::path::Component::ParentDir)) {
        None
    } else {
        std::path::absolute(p).ok()
    }
}

Try / catch

match transport.bind(&path) {
    Err(e) if e.kind() == io::ErrorKind::InvalidInput
        && e.to_string().contains("parent component") => {
        eprintln!("endpoint {:?} contains '..'; fix config", path);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Passing a path with `..` segments to connect(), bind(), or the test helpers — e.g. bind("/tmp/astrid/../service.sock") or a config value assembled by string concatenation with `..`.

Common situations: Endpoint paths built by joining user input or env vars without normalization; configs ported from Unix socket setups with relative paths; typos like "..\pipe" in settings files.

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/edd23cf1d05f8452. Report an issue: GitHub.