astrid-runtime/astrid · error

named-pipe client's effective token belongs to a different o

Error message

named-pipe client's effective token belongs to a different operating-system user

What it means

During accept, the server impersonates the named-pipe client (ImpersonateNamedPipeClient) and reads the effective token's user SID; if it differs from the server's own user SID the client is rejected. This checks the identity the client is actually exercising on the pipe, complementing the process-owner check.

Source

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

fn require_current_user_process_peer(stream: &LocalStream) -> io::Result<VerifiedPeerProcess> {
    let peer = peer_process_identity(stream)?;
    if peer.user_sid.equals(&current_user_sid()?) {
        Ok(peer)
    } else {
        Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "named-pipe peer process belongs to a different operating-system user",
        ))
    }
}

fn require_current_user_effective_client(stream: &LocalStream) -> io::Result<()> {
    let client_sid = effective_client_user_sid(stream)?;
    if client_sid.equals(&current_user_sid()?) {
        Ok(())
    } else {
        Err(io::Error::new(
            io::ErrorKind::PermissionDenied,
            "named-pipe client's effective token belongs to a different operating-system user",
        ))
    }
}

fn effective_client_user_sid(stream: &LocalStream) -> io::Result<OwnedSid> {
    let server = match &stream.inner {
        StreamInner::Server(server) => server,
        StreamInner::Client(_) => {
            return Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "client effective-token validation requires the server pipe end",
            ));
        },
    };
    let impersonated = unsafe { ImpersonateNamedPipeClient(server.as_raw_handle().cast()) };
    if impersonated == 0 {

View on GitHub (pinned to affd8760f4)

Solutions

  1. Start the client under the same user account that owns the server pipe.
  2. Check the client isn't impersonating another user before ConnectNamedPipe/CreateFile on the pipe.
  3. Inspect both accounts with `whoami /user` on each side to confirm the SIDs match.
Defensive patterns

Strategy: validation

Validate before calling

// on the client: ensure the process token owner matches the server account
// e.g. verify with GetTokenInformation(TokenOwner) before opening the pipe

Type guard

fn token_owner_matches(client_sid: &Sid, server_sid: &Sid) -> bool {
    client_sid.equals(server_sid)
}

Try / catch

match listener.accept() {
    Err(e) if e.kind() == io::ErrorKind::PermissionDenied
        && e.to_string().contains("effective token") => {
        eprintln!("client token owner mismatch; dropping connection");
    }
    other => other?,
}

Prevention

When it happens

Trigger: accept() called on a server pipe whose connected client's effective security token belongs to a different OS user — e.g. the client connected using a token of another account, or its impersonation token differs from its process owner.

Common situations: Client running under a service account while the server runs as the logged-in user; client started via scheduled task under a different identity; duplicated/impersonated tokens on the client side.

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