Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions crates/buzz-relay/src/handlers/ingest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1754,6 +1754,21 @@ fn validate_not_before(tag_value: &str) -> Result<u64, &'static str> {
Ok(value)
}

/// Postgres cannot store NUL in `text`/`jsonb` columns, so an event carrying
/// `\u0000` in its content or tag values would otherwise pass validation and
/// then fail at insert time as an internal database error.
fn validate_no_nul_bytes(event: &Event) -> Result<(), &'static str> {
if event.content.contains('\0') {
return Err("content contains a NUL character");
}
for tag in event.tags.iter() {
if tag.as_slice().iter().any(|value| value.contains('\0')) {
return Err("tag value contains a NUL character");
}
}
Ok(())
}

/// Validate the public tag envelope of a NIP-ER `kind:30300` event before it
/// reaches NIP-33 parameterized replacement.
///
Expand Down Expand Up @@ -2020,6 +2035,10 @@ async fn ingest_event_inner(
)));
}

if let Err(e) = validate_no_nul_bytes(&event) {
return Err(IngestError::Rejected(format!("invalid: {e}")));
}

let is_gift_wrap = kind_u32 == KIND_GIFT_WRAP;
if event.pubkey != *auth.pubkey() && !is_gift_wrap {
return Err(IngestError::AuthFailed(
Expand Down Expand Up @@ -3957,6 +3976,43 @@ mod tests {
}
}

#[test]
fn nul_validation_rejects_content_with_nul() {
let event = make_event_with_tags(KIND_STREAM_MESSAGE, "hello\0world", &[]);
assert_eq!(
validate_no_nul_bytes(&event),
Err("content contains a NUL character")
);
}

#[test]
fn nul_validation_rejects_tag_value_with_nul() {
let event = make_event_with_tags(KIND_STREAM_MESSAGE, "hello", &[&["title", "a\0b"]]);
assert_eq!(
validate_no_nul_bytes(&event),
Err("tag value contains a NUL character")
);
}

#[test]
fn nul_validation_rejects_tag_name_with_nul() {
let event = make_event_with_tags(KIND_STREAM_MESSAGE, "hello", &[&["ti\0tle", "a"]]);
assert_eq!(
validate_no_nul_bytes(&event),
Err("tag value contains a NUL character")
);
}

#[test]
fn nul_validation_accepts_clean_event() {
let event = make_event_with_tags(
KIND_STREAM_MESSAGE,
"hello world",
&[&["title", "greeting"]],
);
assert!(validate_no_nul_bytes(&event).is_ok());
}

fn make_dummy_event() -> Event {
let keys = nostr::Keys::generate();
nostr::EventBuilder::new(nostr::Kind::Custom(9), "")
Expand Down