commit 311f53e9b55ed32db27f96aa47532ba904f19f50
parent d8e665b990b1a31ed354842cd46f5d14bf31a508
Author: triesap <tyson@radroots.org>
Date: Sun, 14 Jun 2026 19:17:29 -0700
runtime: harden hidden privacy coverage
Diffstat:
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,