tangle


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

commit 311f53e9b55ed32db27f96aa47532ba904f19f50
parent d8e665b990b1a31ed354842cd46f5d14bf31a508
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 19:17:29 -0700

runtime: harden hidden privacy coverage

Diffstat:
Mcrates/tangle_runtime/src/relay/core.rs | 30+++++++++++++++++++++++++++++-
Mcrates/tangle_runtime/tests/phase2_acceptance_targets.rs | 171++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
2 files changed, 182 insertions(+), 19 deletions(-)

diff --git a/crates/tangle_runtime/src/relay/core.rs b/crates/tangle_runtime/src/relay/core.rs @@ -2769,7 +2769,7 @@ mod tests { } #[test] - fn private_group_offset_lookup_uses_reader_auth() { + fn private_and_hidden_group_offset_lookup_uses_reader_auth() { let owner = signer(7).public_key().clone(); let owner_auth = authenticated_state(7); let unauth = BaseAuthState::new("wss://relay.radroots.test", 60, 600).expect("auth state"); @@ -2802,6 +2802,34 @@ mod tests { .expect("owner offset") .expect("visible"); assert_eq!(visible.id(), private_event.id()); + + relay + .handle_event_with_auth( + signed_group_create_event_with_tags(7, "HiddenFarm", vec![hidden()], 1_714_124_436), + &owner_auth, + ) + .expect("hidden create"); + let hidden_event = signed_event_at( + 7, + 1, + vec![Tag::from_parts("h", &["HiddenFarm"]).expect("h")], + "hidden harvest", + 1_714_124_437, + ); + let pocket = tangle_event_to_pocket(&hidden_event).expect("hidden pocket"); + let offset = StoreOffset::new(relay.store.store_event(&pocket).expect("store hidden")); + + assert_eq!( + relay + .event_by_offset_with_auth(offset, &unauth) + .expect("hidden unauth offset"), + None + ); + let visible = relay + .event_by_offset_with_auth(offset, &owner_auth) + .expect("hidden owner offset") + .expect("hidden visible"); + assert_eq!(visible.id(), hidden_event.id()); } #[test] diff --git a/crates/tangle_runtime/tests/phase2_acceptance_targets.rs b/crates/tangle_runtime/tests/phase2_acceptance_targets.rs @@ -799,11 +799,58 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ .await; assert_count_message( &mut observer, + "private-admins-public-count", + json!({"kinds":[KIND_GROUP_ADMINS], "#d":["PrivateSocket"]}), + 1, + ) + .await; + assert_count_message( + &mut observer, "private-members-public-count", json!({"kinds":[KIND_GROUP_MEMBERS], "#d":["PrivateSocket"]}), 0, ) .await; + assert_count_message( + &mut member_reader, + "private-members-member-count", + json!({"kinds":[KIND_GROUP_MEMBERS], "#d":["PrivateSocket"]}), + 1, + ) + .await; + assert_req_kind_tag_then_eose( + &mut observer, + "private-metadata-public-query", + json!({"kinds":[KIND_GROUP_METADATA], "#d":["PrivateSocket"]}), + KIND_GROUP_METADATA, + "d", + "PrivateSocket", + ) + .await; + assert_req_kind_tag_then_eose( + &mut observer, + "private-admins-public-query", + json!({"kinds":[KIND_GROUP_ADMINS], "#d":["PrivateSocket"]}), + KIND_GROUP_ADMINS, + "d", + "PrivateSocket", + ) + .await; + assert_empty_req( + &mut observer, + "private-members-public-query", + json!({"kinds":[KIND_GROUP_MEMBERS], "#d":["PrivateSocket"]}), + ) + .await; + assert_req_kind_tag_then_eose( + &mut member_reader, + "private-members-member-query", + json!({"kinds":[KIND_GROUP_MEMBERS], "#d":["PrivateSocket"]}), + KIND_GROUP_MEMBERS, + "d", + "PrivateSocket", + ) + .await; send_client_value( &mut observer, @@ -915,26 +962,78 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ "", ); - assert_count_message( - &mut observer, - "hidden-metadata-public-count", - json!({"kinds":[KIND_GROUP_METADATA], "#d":["HiddenSocket"]}), - 0, - ) - .await; - assert_count_message( - &mut owner_reader, - "hidden-metadata-owner-count", - json!({"kinds":[KIND_GROUP_METADATA], "#d":["HiddenSocket"]}), - 1, + let hidden_put = tangle_v2_put_user_event( + FixtureKey::Owner, + "HiddenSocket", + FixtureKey::Member, + 1_714_124_454, ) - .await; - assert_empty_req( - &mut observer, - "hidden-metadata-public-query", - json!({"kinds":[KIND_GROUP_METADATA], "#d":["HiddenSocket"]}), + .expect("hidden put"); + send_client_value( + &mut owner_writer, + json!(["EVENT", event_to_value(&hidden_put)]), ) .await; + assert_ok( + read_relay_value(&mut owner_writer).await, + &hidden_put, + true, + "", + ); + + for (subscription_id, kind) in [ + ("hidden-metadata-public-count", KIND_GROUP_METADATA), + ("hidden-admins-public-count", KIND_GROUP_ADMINS), + ("hidden-members-public-count", KIND_GROUP_MEMBERS), + ] { + assert_count_message( + &mut observer, + subscription_id, + json!({"kinds":[kind], "#d":["HiddenSocket"]}), + 0, + ) + .await; + } + for (subscription_id, kind) in [ + ("hidden-metadata-owner-count", KIND_GROUP_METADATA), + ("hidden-admins-owner-count", KIND_GROUP_ADMINS), + ("hidden-members-owner-count", KIND_GROUP_MEMBERS), + ] { + assert_count_message( + &mut owner_reader, + subscription_id, + json!({"kinds":[kind], "#d":["HiddenSocket"]}), + 1, + ) + .await; + } + for (subscription_id, kind) in [ + ("hidden-metadata-public-query", KIND_GROUP_METADATA), + ("hidden-admins-public-query", KIND_GROUP_ADMINS), + ("hidden-members-public-query", KIND_GROUP_MEMBERS), + ] { + assert_empty_req( + &mut observer, + subscription_id, + json!({"kinds":[kind], "#d":["HiddenSocket"]}), + ) + .await; + } + for (subscription_id, kind) in [ + ("hidden-metadata-owner-query", KIND_GROUP_METADATA), + ("hidden-admins-owner-query", KIND_GROUP_ADMINS), + ("hidden-members-owner-query", KIND_GROUP_MEMBERS), + ] { + assert_req_kind_tag_then_eose( + &mut owner_reader, + subscription_id, + json!({"kinds":[kind], "#d":["HiddenSocket"]}), + kind, + "d", + "HiddenSocket", + ) + .await; + } send_client_value( &mut observer, @@ -954,11 +1053,20 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ read_relay_value(&mut owner_reader).await, json!(["EOSE", "hidden-owner-live"]) ); + send_client_value( + &mut member_reader, + json!(["REQ", "hidden-member-live", {"kinds":[1], "#h":["HiddenSocket"]}]), + ) + .await; + assert_eq!( + read_relay_value(&mut member_reader).await, + json!(["EOSE", "hidden-member-live"]) + ); let hidden_note = tangle_v2_group_event( FixtureKey::Owner, "HiddenSocket", - 1_714_124_454, + 1_714_124_455, 1, "hidden harvest", ) @@ -979,6 +1087,11 @@ async fn websocket_private_and_hidden_groups_do_not_leak_through_query_count_or_ "hidden-owner-live", &hidden_note, ); + assert_live_event( + read_relay_value(&mut member_reader).await, + "hidden-member-live", + &hidden_note, + ); expect_no_relay_message(&mut observer).await; assert_count_message( &mut observer, @@ -2210,6 +2323,28 @@ async fn assert_req_event_then_eose( ); } +async fn assert_req_kind_tag_then_eose( + socket: &mut TestWebSocket, + subscription_id: &str, + filter: Value, + kind: u32, + tag_name: &str, + tag_value: &str, +) { + send_client_value(socket, json!(["REQ", subscription_id, filter])).await; + assert_relay_event_kind_tag( + read_relay_value(socket).await, + subscription_id, + kind, + tag_name, + tag_value, + ); + assert_eq!( + read_relay_value(socket).await, + json!(["EOSE", subscription_id]) + ); +} + fn assert_relay_ok(message: RelayMessage, event: &Event, accepted: bool, reason: &str) { assert_eq!( message,