Fix routing for empty binding key to topic exchange#16271
Open
Conversation
b1fbae8 to
f9e5fd7
Compare
There was a problem hiding this comment.
Pull request overview
Fixes incorrect topic exchange routing for empty routing keys/binding keys in the Khepri topic-trie projection by introducing an exchange-scoped trie root (v5 projection) to prevent cross-exchange binding leakage.
Changes:
- Add a new Khepri topic binding projection v5 with exchange-specific root node IDs and a new
topic_binding_projection_v5feature flag. - Update topic routing (
rabbit_db_topic_exchange) to select per-version ETS table names and start traversal from the version-appropriate root node. - Update/add tests to enable v5 and cover “zero words” (empty) topic routing/binding keys.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| deps/rabbit/src/rabbit_khepri.erl | Adds v5 projection support (table naming, root scoping), new feature-flag callbacks, and bumps supported trie version. |
| deps/rabbit/src/rabbit_db_topic_exchange.erl | Routes via versioned trie/binding ETS tables and uses an exchange-specific root for v5+. |
| deps/rabbit/src/rabbit_core_ff.erl | Introduces topic_binding_projection_v5 feature flag (depends on v4) with enable/post-enable callbacks. |
| deps/rabbit/test/rabbit_db_topic_exchange_SUITE.erl | Switches test suite to v5 ETS tables and enables the v5 feature flag in setup. |
| deps/rabbit/test/bindings_SUITE.erl | Adds regression test to validate empty routing key/binding key behavior across multiple topic exchanges. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Fixes #16221 ## What? Fix a bug in the Khepri v4 topic trie projection where messages published with an empty routing key to a topic exchange were incorrectly routed to queues bound to other topic exchanges with an empty binding key. ## Why? The MQTT 5.0 spec states: > All Topic Names and Topic Filters MUST be at least one character long In contrast, the AMQP 0.9.1 spec state: > The routing key used for a topic exchange MUST consist of **zero** or more words delimited by dots. Each word may contain the letters A-Z and a-z and digits 0-9. The routing pattern follows the same rules as the routing key with the addition that * matches a single word, and # matches zero or more words Hence zero words, i.e. empty routing keys and empty binding keys, are expliclity allowed for the topic exchange type. In the Khepri v4 projection, the topic trie used a global `root` atom as the initial node ID for all topic exchanges. When a binding was created with an empty binding key (`<<>>`), `split_topic_key_binary/1` returned an empty list (`[]`). The trie traversal stopped immediately at the root, meaning the binding was inserted into the `rabbit_khepri_topic_binding_v4` ETS table with the global `root` atom as the `LeafNodeId`. Because the `LeafNodeId` lacked any exchange-specific context, all empty bindings across all topic exchanges were attached to the exact same global `root` node. Consequently, when a message was published with an empty routing key to any topic exchange, the routing logic (`trie_bindings/3`) would scan the ETS table starting at the global `root` node and incorrectly match bindings belonging to completely unrelated topic exchanges. ## How? To fix this and ensure exchange isolation, the conceptual root of the trie was changed from the global `root` atom to an exchange-specific tuple: `{root, XSrc}` (where `XSrc` is `{VHost, ExchangeName}`). - `rabbit_khepri:trie_follow_down_create/3` and `trie_follow_down_get_path/3` now initialize their trie traversal with `{root, XSrc}`. - `rabbit_db_topic_exchange:trie_match/5` now initiates routing from `{root, {VHost, XName}}`. This structural change guarantees that empty bindings are isolated per exchange in the bindings ETS table, completely resolving the cross-exchange leakage while fully supporting AMQP 0-9-1's allowance for empty binding keys without breaking backward compatibility.
This commit introduces a new feature flag, `topic_binding_projection_v5`, and a corresponding Khepri projection version (v5) to deploy the exchange isolation fix for empty binding keys.
f9e5fd7 to
05a2191
Compare
dumbbell
requested changes
Apr 30, 2026
Collaborator
dumbbell
left a comment
There was a problem hiding this comment.
The patch looks good to me. I only have a minor style comment.
Member
Author
|
I did some more manual testing including:
In both cases after enabling all feature flags, the state with regards to the projection tables looked as expected. |
michaelklishin
approved these changes
Apr 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #16221
What?
Fix a bug in the Khepri v4 topic trie projection where messages published with an empty routing key to a topic exchange were incorrectly routed to queues bound to other topic exchanges with an empty binding key.
Why?
The MQTT 5.0 spec states:
In contrast, the AMQP 0.9.1 spec state:
Hence zero words, i.e. empty routing keys and empty binding keys, are expliclity allowed for the topic exchange type.
In the Khepri v4 projection, the topic trie used a global
rootatom as the initial node ID for all topic exchanges. When a binding was created with an empty binding key (<<>>),split_topic_key_binary/1returned an empty list ([]). The trie traversal stopped immediately at the root, meaning the binding was inserted into therabbit_khepri_topic_binding_v4ETS table with the globalrootatom as theLeafNodeId.Because the
LeafNodeIdlacked any exchange-specific context, all empty bindings across all topic exchanges were attached to the exact same globalrootnode. Consequently, when a message was published with an empty routing key to any topic exchange, the routing logic (trie_bindings/3) would scan the ETS table starting at the globalrootnode and incorrectly match bindings belonging to completely unrelated topic exchanges.How?
To fix this and ensure exchange isolation, the conceptual root of the trie was changed from the global
rootatom to an exchange-specific tuple:{root, XSrc}(whereXSrcis{VHost, ExchangeName}).rabbit_khepri:trie_follow_down_create/3andtrie_follow_down_get_path/3now initialize their trie traversal with{root, XSrc}.rabbit_db_topic_exchange:trie_match/5now initiates routing from{root, {VHost, XName}}.This structural change guarantees that empty bindings are isolated per exchange in the bindings ETS table, completely resolving the cross-exchange leakage while fully supporting AMQP 0-9-1's allowance for empty binding keys without breaking backward compatibility.
TODOs