unicity-aos/aos-ce · warning

Dropped ingress message: malformed principal

Error message

Dropped ingress message: malformed principal {p:?}; connection stays unbound

What it means

handle_ingress asked decide_ingress to classify an inbound message's principal, and the decision came back Drop with DropReason::InvalidPrincipal, meaning the message's principal could not be parsed/validated. The library logs a warning and drops the message; the connection deliberately stays unbound so a malformed identity can never claim or mutate a binding. Nothing is forwarded downstream.

Solutions

  1. Inspect the logged {p:?} debug value to see exactly what was parsed as the principal and fix the client to emit a valid principal
  2. Validate the principal field on the client side before sending ingress messages
  3. Check for protocol/version mismatch between the client and capsule-cli and upgrade the client
  4. If the principal is legitimately optional, send the message without a principal field rather than a malformed one

Example fix

// before
send(msg_with_principal("user-42")) // not a valid principal
// after
send(msg_with_principal(Principal::from_str("user-42")?)) // validated principal
Defensive patterns

Strategy: validation

Validate before calling

fn valid_principal(p: &str) -> bool { !p.trim().is_empty() && p.parse::<Principal>().is_ok() }
if !valid_principal(&msg.principal) { return Err("invalid principal"); }

Type guard

fn as_principal(v: &serde_json::Value) -> Option<Principal> {
    v.as_str().and_then(|s| Principal::from_str(s).ok())
}

Prevention

When it happens

Trigger: A client sends an ingress message whose principal field is missing, empty, or not a well-formed principal; decide_ingress returns IngressDecision::Drop { reason: DropReason::InvalidPrincipal(p) } and handle_ingress logs this warning and returns an empty outcome.

Common situations: Hand-rolled clients emitting a hand-typed principal string; serialization/version drift between client and proxy (old clients sending an id where a principal struct is expected); test scripts posting raw JSON over the IPC socket with a bogus principal field.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13). Data as JSON: /api/errors/386bfba83817270a. Report an issue: GitHub.

Appendix: source

Thrown at capsules/capsule-cli/src/lib.rs:567

    let msg = match serde_json::from_slice::<serde_json::Value>(bytes) {
        Ok(v) => v,
        Err(_) => {
            log::warn("Received malformed IPC payload from socket");
            return empty;
        }
    };

    let message_principal = msg.get("principal").and_then(|p| p.as_str());

    // Resolve the binding decision first — a conflicting or malformed
    // principal is dropped before any forward, and never mutates the binding.
    let (forward_as, newly_bound) = match decide_ingress(current_binding, message_principal) {
        IngressDecision::Bind(p) => (p.clone(), Some(p)),
        IngressDecision::ForwardAs(p) => (p, None),
        IngressDecision::Drop { reason } => {
            match reason {
                DropReason::InvalidPrincipal(p) => log::warn(format!(
                    "Dropped ingress message: malformed principal {p:?}; connection stays unbound"
                )),
                DropReason::PrincipalConflict { bound, claimed } => log::warn(format!(
                    "Dropped ingress message: connection bound to {bound:?} but message claimed {claimed:?}"
                )),
            }
            return empty;
        }
    };

    let (Some(topic), Some(payload)) = (
        msg.get("topic").and_then(|t| t.as_str()),
        msg.get("payload"),
    ) else {
        // No forwardable body, but the principal still binds the connection
        // (e.g. a bare handshake establishes identity for connect-tracking).
        // Nothing is forwarded, so the connection's session is never retargeted.
        log::warn("Ingress message has no topic/payload; binding only, nothing forwarded");

View on GitHub (pinned to f6f22024fb)