tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit 03dc98ef04beb28c526f317735d00ee5cba5cd8b
parent 068b97bd1f55c9d066e074c9fc8778cf8a25e613
Author: triesap <tyson@radroots.org>
Date:   Tue, 11 Aug 2026 01:45:09 +0000

runtime: add outbound message sanitizer hook

Diffstat:
Mcrates/tangle_runtime/src/relay/outbound.rs | 40++++++++++++++++++++++++++++++++++++++++
Mcrates/tangle_runtime/src/runtime.rs | 11+++++++++++
Mcrates/tangle_runtime/src/session.rs | 4+++-
3 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/crates/tangle_runtime/src/relay/outbound.rs b/crates/tangle_runtime/src/relay/outbound.rs @@ -32,6 +32,19 @@ impl RuntimeRelayMessage { } } + pub(crate) fn map_protocol(self, mapper: impl FnOnce(RelayMessage) -> RelayMessage) -> Self { + match self { + Self::Event { + subscription_id, + event, + } => Self::Event { + subscription_id, + event, + }, + Self::Protocol(message) => Self::Protocol(mapper(message)), + } + } + pub(crate) fn into_protocol_control_message(self) -> Result<RelayMessage, BaseRelayError> { match self { Self::Event { .. } => Err(BaseRelayError::error( @@ -137,4 +150,31 @@ mod tests { relay_message_to_value(&message) ); } + + #[test] + fn protocol_mapping_never_rewrites_event_payloads() { + let subscription_id = SubscriptionId::new("outbound-map").expect("subscription"); + let event = tangle_v2_event( + FixtureKey::Member, + 1_714_124_433, + 1, + Vec::new(), + "unchanged", + ) + .expect("event"); + let pocket = tangle_event_to_pocket(&event).expect("pocket"); + let event_message = RuntimeRelayMessage::event(subscription_id.clone(), pocket); + let mapped_event = event_message + .clone() + .map_protocol(|_| RelayMessage::Notice("must not replace event payload".to_owned())); + assert_eq!(mapped_event, event_message); + + let mapped_protocol = + RuntimeRelayMessage::from(RelayMessage::Notice("internal diagnostic".to_owned())) + .map_protocol(|_| RelayMessage::Notice("public code".to_owned())); + assert_eq!( + mapped_protocol, + RuntimeRelayMessage::from(RelayMessage::Notice("public code".to_owned())) + ); + } } diff --git a/crates/tangle_runtime/src/runtime.rs b/crates/tangle_runtime/src/runtime.rs @@ -110,6 +110,10 @@ pub trait RelayRuntimeHooks: Send + Sync { ) -> RelayEventProjectionDecision { RelayEventProjectionDecision::Emit } + + fn sanitize_public_message(&self, message: RelayMessage) -> RelayMessage { + message + } } #[derive(Debug, Default)] @@ -1613,6 +1617,13 @@ impl RelayRuntimeHandle { self.inner.readiness.clone() } + pub(crate) fn sanitize_public_message( + &self, + message: RuntimeRelayMessage, + ) -> RuntimeRelayMessage { + message.map_protocol(|message| self.inner.hooks.sanitize_public_message(message)) + } + pub fn limits(&self) -> TangleRuntimeLimits { self.inner.limits } diff --git a/crates/tangle_runtime/src/session.rs b/crates/tangle_runtime/src/session.rs @@ -481,7 +481,9 @@ impl TangleWebSocketSession { } fn send_relay_message(&self, message: RuntimeRelayMessage) -> Result<(), TangleSessionControl> { - let text = message + let text = self + .runtime + .sanitize_public_message(message) .encode() .map_err(|_| TangleSessionControl::Close(outbound_encode_close_message()))?; self.outbound