mirror of
https://github.com/block/buzz.git
synced 2026-10-28 13:01:10 +00:00
fix(relay): reject observer frames from banned signers over HTTP
Observer frames return before the ingest write-path ban gate, and NIP-98 auth does not consult bans, so a banned owner could keep sending control frames through POST /events. Check the authoring pubkey's ban state before accepting the frame, failing closed on lookup errors. Generated with AI Co-Authored-By: rp1 <bot@rp1.run> Signed-off-by: Prem Pillai <prem@squareup.com>
This commit is contained in:
@@ -2439,6 +2439,27 @@ async fn ingest_event_inner(
|
||||
}
|
||||
|
||||
if kind_u32 == KIND_AGENT_OBSERVER_FRAME {
|
||||
// Observer frames return before the write-path ban gate below, and
|
||||
// NIP-98 auth never consults bans. Re-check the authoring pubkey here so
|
||||
// a banned owner cannot keep sending control frames over HTTP.
|
||||
match state
|
||||
.db
|
||||
.moderation_restriction_state(tenant.community(), auth.pubkey().as_bytes())
|
||||
.await
|
||||
{
|
||||
Ok(r) if r.banned => {
|
||||
return Err(IngestError::AuthFailed(
|
||||
"blocked: you are banned from this community".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
// Fail closed: a DB error must not let a banned actor through.
|
||||
return Err(IngestError::Internal(format!(
|
||||
"error: internal error checking restriction state: {e}"
|
||||
)));
|
||||
}
|
||||
}
|
||||
super::event::ingest_agent_observer_event(state, tenant, &event, None).await?;
|
||||
emit(
|
||||
tracer,
|
||||
@@ -3736,6 +3757,52 @@ mod postgres_tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires Postgres and Redis"]
|
||||
async fn http_observer_frame_rejects_banned_owner_control_frame() {
|
||||
let (state, tenant, agent, owner) = observer_ingest_fixture().await;
|
||||
let encrypted = buzz_core::observer::encrypt_observer_payload(
|
||||
&owner,
|
||||
&agent.public_key(),
|
||||
&serde_json::json!({"kind": "cancel"}),
|
||||
)
|
||||
.expect("encrypt control payload");
|
||||
let control = || {
|
||||
buzz_sdk::build_agent_observer_frame(
|
||||
&agent.public_key().to_hex(),
|
||||
&agent.public_key().to_hex(),
|
||||
buzz_core::observer::OBSERVER_FRAME_CONTROL,
|
||||
&encrypted,
|
||||
)
|
||||
.expect("build control frame")
|
||||
.sign_with_keys(&owner)
|
||||
.expect("sign control frame")
|
||||
};
|
||||
|
||||
assert!(
|
||||
ingest_observer_test_event(&state, &tenant, &owner, control())
|
||||
.await
|
||||
.is_ok(),
|
||||
"unbanned owner control frame should be accepted"
|
||||
);
|
||||
|
||||
state
|
||||
.db
|
||||
.ban_community_member(
|
||||
tenant.community(),
|
||||
&owner.public_key().to_bytes(),
|
||||
&Keys::generate().public_key().to_bytes(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("ban owner");
|
||||
assert!(matches!(
|
||||
ingest_observer_test_event(&state, &tenant, &owner, control()).await,
|
||||
Err(IngestError::AuthFailed(message)) if message.contains("banned")
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires Postgres and Redis"]
|
||||
async fn http_unknown_ephemeral_kind_remains_restricted() {
|
||||
|
||||
Reference in New Issue
Block a user