{"record":{"id":"6ec3f363ee1d2666","repo":"unicity-aos/aos-ce","slug":"hook-bridge-dropping-response-with-mismatched-route-or","errorCode":null,"errorMessage":"hook-bridge: dropping response with mismatched route or principal on {reply_topic}","messagePattern":"hook-bridge: dropping response with mismatched route or principal on (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"capsules/capsule-hook-bridge/src/lib.rs","lineNumber":205,"sourceCode":"        }\n        let remaining = if batch.values.is_empty() {\n            HOOK_COLLECT_DEADLINE_MS - elapsed_ms\n        } else {\n            HOOK_QUIESCENCE_MS.min(HOOK_COLLECT_DEADLINE_MS - elapsed_ms)\n        };\n        match subscription.recv(remaining) {\n            Ok(poll) if poll.messages.is_empty() => break,\n            Ok(poll) => {\n                if poll.dropped != 0 || poll.lagged != 0 {\n                    batch.complete = false;\n                    log::warn(format!(\n                        \"hook-bridge: response fan-out on {reply_topic} lost messages\"\n                    ));\n                }\n                for message in poll.messages {\n                    if message.topic != reply_topic || message.principal.verified() != principal {\n                        batch.complete = false;\n                        log::warn(format!(\n                            \"hook-bridge: dropping response with mismatched route or principal on {reply_topic}\"\n                        ));\n                        continue;\n                    }\n                    if message.payload.len() > MAX_HOOK_RESPONSE_BYTES {\n                        batch.complete = false;\n                        log::warn(format!(\n                            \"hook-bridge: dropping oversized reply on {reply_topic}\"\n                        ));\n                        continue;\n                    }\n                    match serde_json::from_str(&message.payload) {\n                        Ok(value) => batch.values.push(value),\n                        Err(error) => {\n                            batch.complete = false;\n                            log::warn(format!(\n                                \"hook-bridge: dropping malformed reply on {reply_topic}: {error}\"\n                            ));","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/capsules/capsule-hook-bridge/src/lib.rs#L187-L223","documentation":"While fanning out hook responses, collect_responses validates each polled message against the expected reply topic and the original caller's verified principal. A message addressed to a different topic or emitted under a different principal is rejected: batch.complete is set to false and this warning is logged, and the message is skipped (continue). This guards against cross-talk between concurrent hook dispatches and spoofed responses.","triggerScenarios":"Raised in collect_responses (called from dispatch_hook) when a polled message's topic != reply_topic, or message.principal.verified() != the principal captured at dispatch time — the reply came on the wrong route or from an unauthenticated/mismatched sender.","commonSituations":"Multiple concurrent hook dispatches sharing a reply topic namespace with a collision; a responder replying to a stale or wrong reply_topic from a previous dispatch; a compromised or misconfigured responder sending replies under a different principal; reusing subscription handlers across dispatches without re-filtering.","solutions":["Make reply_topic unique per dispatch (include a dispatch/request id) so responses can't cross routes.","Ensure every responder echoes the exact reply_topic it received and replies under its own verified principal that matches the expected one.","Since batch.complete is false, retry the dispatch or report incomplete results rather than treating partial responses as authoritative.","Audit responder configurations to eliminate stale reply topics from previous dispatches."],"exampleFix":"// before\nlet reply_topic = \"oracle-replies\"; // shared across dispatches\n// after\nlet reply_topic = format!(\"oracle-replies/{}\", dispatch_id);","handlingStrategy":"validation","validationCode":"// Filter replies before processing\nfor message in poll.messages {\n    if message.topic != reply_topic || message.principal.verified() != principal {\n        continue; // mismatched route or principal — reject\n    }\n}\n// Better: make the route unique up front\nlet reply_topic = format!(\"hook-replies/{}\", dispatch_id);","typeGuard":null,"tryCatchPattern":"// Treat partial batches as incomplete and retry or surface partials\nmatch dispatch_hook(hook) {\n    Ok(batch) if batch.complete => use(batch),\n    Ok(batch) => handle_partial(batch),\n    Err(e) => report(e),\n}","preventionTips":["Use per-dispatch unique reply topics to prevent cross-talk.","Ensure responders echo the exact reply_topic they were given.","Verify responders reply under the principal the dispatcher expects."],"tags":["rust","routing","principal","messaging"],"backgroundTag":"type-mismatch","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"}