{"record":{"id":"ea8e0b245e6b9d14","repo":"unicity-aos/aos-ce","slug":"dropped-ingress-message-connection-bound-to-bound-but","errorCode":null,"errorMessage":"Dropped ingress message: connection bound to {bound:?} but message claimed {claimed:?}","messagePattern":"Dropped ingress message: connection bound to (.+?) but message claimed (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"capsules/capsule-cli/src/lib.rs","lineNumber":570,"sourceCode":"        Err(_) => {\n            log::warn(\"Received malformed IPC payload from socket\");\n            return empty;\n        }\n    };\n\n    let message_principal = msg.get(\"principal\").and_then(|p| p.as_str());\n\n    // Resolve the binding decision first — a conflicting or malformed\n    // principal is dropped before any forward, and never mutates the binding.\n    let (forward_as, newly_bound) = match decide_ingress(current_binding, message_principal) {\n        IngressDecision::Bind(p) => (p.clone(), Some(p)),\n        IngressDecision::ForwardAs(p) => (p, None),\n        IngressDecision::Drop { reason } => {\n            match reason {\n                DropReason::InvalidPrincipal(p) => log::warn(format!(\n                    \"Dropped ingress message: malformed principal {p:?}; connection stays unbound\"\n                )),\n                DropReason::PrincipalConflict { bound, claimed } => log::warn(format!(\n                    \"Dropped ingress message: connection bound to {bound:?} but message claimed {claimed:?}\"\n                )),\n            }\n            return empty;\n        }\n    };\n\n    let (Some(topic), Some(payload)) = (\n        msg.get(\"topic\").and_then(|t| t.as_str()),\n        msg.get(\"payload\"),\n    ) else {\n        // No forwardable body, but the principal still binds the connection\n        // (e.g. a bare handshake establishes identity for connect-tracking).\n        // Nothing is forwarded, so the connection's session is never retargeted.\n        log::warn(\"Ingress message has no topic/payload; binding only, nothing forwarded\");\n        return IngressOutcome {\n            newly_bound,\n            session_id: None,","sourceCodeStart":552,"sourceCodeEnd":588,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/capsules/capsule-cli/src/lib.rs#L552-L588","documentation":"decide_ingress returned Drop with DropReason::PrincipalConflict: the message's claimed principal does not match the principal already bound to this connection. The library drops the message and returns an empty outcome, keeping the original binding intact — this prevents an authenticated connection from being hijacked by sending messages claiming a different identity.","triggerScenarios":"A client reuses an already-bound connection and sends a message whose principal differs from the one bound at IngressDecision::Bind; decide_ingress returns Drop { reason: PrincipalConflict { bound, claimed } } and handle_ingress logs this warning.","commonSituations":"Connection pooling bugs where one client's socket is handed to another process; a client restarted with different credentials but kept the old socket; load balancers multiplexing sessions onto one connection; credentials rotated mid-session.","solutions":["Compare the logged {bound:?} vs {claimed:?} to see which identity mismatched and fix the client to send messages under its bound principal","Have the client open a new connection instead of reusing one bound to a different principal","If the principal legitimately changed, close and re-establish the connection so it rebinds","Audit connection pooling/multiplexing code for socket sharing across identities"],"exampleFix":"// before\nconn.send(msg_claiming(other_principal)) // conflicts with bound principal\n// after\nlet conn = Connection::connect_as(my_principal)?;\nconn.send(msg);","handlingStrategy":"validation","validationCode":"if let Some(bound) = conn.bound_principal() {\n    assert_eq!(bound, &msg.principal, \"message principal must match bound connection principal\");\n}","typeGuard":"fn principal_matches(conn: &Connection, claimed: &Principal) -> bool {\n    conn.bound_principal().map_or(false, |b| b == *claimed)\n}","tryCatchPattern":null,"preventionTips":["Always send messages on a connection under the principal it was bound with","Reconnect with the new principal after credential rotation instead of reusing the socket","Avoid sharing connections across processes or identities","Treat principal-conflict warnings as possible connection-pooling bugs"],"tags":["ingress","principal","conflict","security"],"backgroundTag":"invalid-state-transition","analyzedSha":"f6f22024fb1e8d122f28a1b4a9f75aee448ae839","analyzedAt":"2026-09-13T03:04:44.565Z","contentChangedAt":"2026-09-13T03:04:44.565Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}