astrid-runtime/astrid · error

named-pipe owner is not the current operating-system user

Error message

named-pipe owner is not the current operating-system user

What it means

During connect/accept on Windows, the library reads the named pipe's security descriptor via GetSecurityInfo and requires the pipe's owner SID to equal the SID of the current operating-system user. This error means the pipe is owned by a different user (or the owner could not be resolved), so the client refuses to talk to a pipe it does not own — a defense against symlink/squatting attacks where an attacker pre-creates the pipe. The failure surfaces as io::Error with ErrorKind::PermissionDenied from validate_pipe_security.

Source

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

    };
    if status != 0 {
        return Err(io::Error::from_raw_os_error(
            i32::try_from(status).unwrap_or(i32::MAX),
        ));
    }
    if descriptor.is_null() {
        return Err(io::Error::other(
            "Windows returned no named-pipe security descriptor",
        ));
    }
    let descriptor_allocation = LocalAllocation(descriptor);

    // SAFETY: GetSecurityInfo returned this non-null descriptor, and
    // `descriptor_allocation` keeps it live through validation.
    unsafe { validate_descriptor_control(descriptor) }?;

    if owner.is_null() || unsafe { EqualSid(owner, current.as_psid()) } == 0 {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "named-pipe owner is not the current operating-system user",
        ));
    }
    if dacl.is_null() {
        return Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "named-pipe has a null or missing DACL",
        ));
    }

    // SAFETY: `dacl` points into the descriptor allocation returned by
    // GetSecurityInfo, which remains live and unmodified through
    // `descriptor_allocation`. The parser validates and bounds the ACL before
    // exposing any borrowed ACE or SID.
    let dacl = unsafe {
        ValidatedAcl::from_raw(
            dacl,

View on GitHub (pinned to affd8760f4)

Solutions

  1. Recreate the pipe from the same OS user account that the current process runs as (the library's pipe creation path sets the owner correctly).
  2. Check who owns the pipe with `Get-Acl \\.\pipe\<name>` in PowerShell; if it is a stale pipe from another account, close that process or delete the pipe endpoint.
  3. Verify you are not mixing elevated and non-elevated sessions; run both creator and consumer under the same user, or recreate the pipe after switching accounts.
  4. If the pipe path is derived from config, ensure it points at your own instance's pipe, not another user's.

Example fix

// before: pipe created by an elevated shell, client runs unelevated -> owner mismatch
// after: create the pipe in the same session/user as the client
let transport = LocalTransport::connect(&pipe_path)?; // pipe must be (re)created by current user
// e.g. start the server without 'Run as administrator', or start the client
// with the same account that owns the pipe
Defensive patterns

Strategy: validation

Validate before calling

// Before connecting, check the pipe's owner matches the current user (PowerShell preflight or in-process):
// powershell: (Get-Acl \\.\pipe\myapp).Owner -eq "<DOMAIN>\\<user>"
let expected = whoami::username(); // or query token via GetTokenInformation
if !pipe_owner_matches_current_user(&pipe_path) {
    eprintln!("pipe {pipe_path} is not owned by {expected}; recreate it");
}

Try / catch

match std::fs::metadata? -> no; use: match connect() {
    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => {
        // owner mismatch: recreate pipe under current user or surface clear guidance
        recreate_pipe_as_current_user()?;
        connect()?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling connect() or accept() on a named pipe whose security descriptor owner SID does not match the current user's token SID (EqualSid returns 0), or when the descriptor has a null owner pointer.

Common situations: The pipe was created by a service or another user account (e.g. created earlier under an elevated/admin session, or a scheduled task) and the current process runs as a different account; an attacker planted a pipe at the expected path; running under runas/sudo-like account switching so the connecting user differs from the creator.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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