astrid-runtime/astrid · error

client effective-token validation requires the server pipe…

Error message

client effective-token validation requires the server pipe end

What it means

effective_client_user_sid works by impersonating the client, which is only possible from the server end of a named pipe (it needs the server handle obtained from CreateNamedPipe). When handed a client-end LocalStream it returns InvalidInput, because effective-token validation is meaningless from the client side.

Solutions

  1. Only call effective-token validation on streams returned by accept() on the server side.
  2. On the client end, validate the server differently (e.g. process-owner checks) or skip effective-token checks entirely.
  3. Track which end of the pipe you hold; if you need the check, forward the server stream handle to the code performing validation.

Example fix

// before
let stream = LocalTransport::connect(&path)?;
let sid = peer_is_current_user(&stream)?; // InvalidInput
// after
let stream = listener.accept()?; // server end only
let sid = peer_is_current_user(&stream)?;
Defensive patterns

Strategy: type-guard

Type guard

fn is_server_end(stream: &LocalStream) -> bool {
    matches!(stream.inner, StreamInner::Server(_))
}

Try / catch

if !is_server_end(&stream) {
    return Err(io::Error::new(
        io::ErrorKind::InvalidInput,
        "effective-token check is only valid on the server pipe end",
    ));
}

Prevention

When it happens

Trigger: Calling peer_is_current_user or require_current_user_effective_client on a LocalStream created by connect() (StreamInner::Client) rather than one obtained from accept() on a bound server (StreamInner::Server).

Common situations: Client code that reuses a shared validation helper on its own stream; copying server-side auth checks into client code; wrapping connect() and accept() results in the same type and losing track of which end you hold.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

}

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 {
        return Err(last_error(
            "failed to impersonate the connected named-pipe client",
        ));
    }
    let guard = ImpersonationGuard { active: true };

    let mut token = ptr::null_mut();
    let opened = unsafe { OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, 1, &raw mut token) };
    if opened == 0 || token.is_null() {
        return Err(last_error(
            "failed to open impersonated named-pipe client token",

View on GitHub (pinned to affd8760f4)