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
- Inspect the logged {p:?} debug value to see exactly what was parsed as the principal and fix the client to emit a valid principal
- Validate the principal field on the client side before sending ingress messages
- Check for protocol/version mismatch between the client and capsule-cli and upgrade the client
- 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
- Validate principal format client-side before sending ingress messages
- Log the rejected principal value when debugging protocol mismatches
- Pin client/proxy protocol versions to avoid serialization drift
- Never send empty or placeholder principal strings
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Dropped ingress message: connection bound to
- hook-adapter-oracle: dropping mismatched context reply on
- canonical document exceeds bound
- must not be empty
- must be an absolute path
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)