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:
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