fix(security): prevent cluster heartbeat address hijack without mTLS - #166
Conversation
On plaintext clusters the shared CLUSTER_SECRET lets any holder authenticate as an existing member node_id. Heartbeat payloads used to overwrite peer control/media addresses and trigger outbound mesh connections to attacker infrastructure. Only apply heartbeat-supplied routing addresses when cluster mTLS is enabled (certificate binds the connection to node_id). Plaintext clusters keep addresses from join/topology refresh only. Co-authored-by: Alexander Wagner <info@alexanderwagnerdev.com>
|
ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7e61db4. Configure here.
| tokio::spawn(async move { | ||
| let _ = hub.connect_peer(mid, &m).await; | ||
| }); | ||
| } |
There was a problem hiding this comment.
Followers never learn new member addresses
High Severity
On plaintext clusters, heartbeat handling no longer writes meta, network.nodes, or media peers, and nothing else seeds those caches when membership grows. Existing followers therefore never learn a new joiner’s control or media addresses: they stop sending heartbeats to that node (so the joiner can mark them DOWN), skip subscribe_remote when it owns a stream, and omit it from standby placement. Join and topology refresh only cover the leader, the joiner, and process restart—not a running third member. Default tls_enabled is off, so this hits typical multi-node deployments.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 7e61db4. Configure here.


Security review fix
Closes #165
Finding: Cluster heartbeat member address hijack (High)
src/cluster/manager.rsCLUSTER_TLS_ENABLED=false), any holder of the sharedCLUSTER_SECRETcan authenticate as an existing membernode_idand sendHeartbeatframes with attacker-controlledcontrol_addr/media_addr. Receiving nodes overwrite peer routing metadata and open outbound media mesh connections to the attacker, enabling interception/injection of inter-node control and RTMP relay traffic.node_id). Plaintext clusters keep addresses fromJoinRequest(withadmin_proof) and topology refresh only.Changes
heartbeat_routing_addrs()helper to gate address updates ontls_enabled.meta.set_addrs,network.upsert_node, andhub.connect_peerfrom heartbeat when mTLS is off.Operational note: Production cluster deployments should still enable mTLS and restrict
CLUSTER_BIND/CLUSTER_MEDIA_BINDto a private network.Note
Medium Risk
Security-critical cluster routing change with targeted tests; behavior change for plaintext deployments that relied on heartbeat-driven address updates.
Overview
Fixes a high-severity issue on plaintext clusters (
CLUSTER_TLS_ENABLED=false): any peer withCLUSTER_SECRETcould heartbeat as anothernode_idand push fakecontrol_addr/media_addr, causing nodes to retarget control routing and open media mesh connections to an attacker.Introduces
heartbeat_routing_addrs()so heartbeat payloads only update routing when mTLS is on (identity is certificate-bound). On plaintext, health still uses addresses resolved from already registered metadata (join / topology), not peer-reported values.meta.set_addrs,network.upsert_node, andconnect_peerfrom heartbeats run only when TLS is enabled.Adds unit tests for plaintext (ignore reported addrs) vs mTLS (accept reported addrs).
Cargo.lockbumps several transitive crate versions.Reviewed by Cursor Bugbot for commit 7e61db4. Bugbot is set up for automated code reviews on this repo. Configure here.