From ec22cdb11762d0737950249eebaebf261038f13c Mon Sep 17 00:00:00 2001 From: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> Date: Fri, 26 Jun 2026 20:36:59 -0400 Subject: [PATCH] feat(relay): RelayInfo::build static-input fence (conformance NIP-11 row) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The conformance obligation for the NIP-11 surface: RelayInfo::build must not grow unscoped DB/search/audit inputs, so an unauthenticated NIP-11 read can never become a cross-community enumeration oracle. Binds RelayInfo::build to its exact allowed signature via a const fn pointer. Adding a &Db / &AppState / search / audit input makes the function-pointer type stop matching and breaks the build at the fence — a silent cross-tenant leak becomes a hard compile error, deny-lint style. Adversarially proven: injecting a &AppState param into build() produces error[E0308] mismatched types at the fence const (plus E0061 at the call sites); reverted to confirm the fence, not the call sites alone, is the guard. buzz-relay package 374 green at --test-threads=1. (cherry picked from commit 76a4044c7cfb1c96a6817be1e81c7ae42d1ea3da) Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com> --- crates/buzz-relay/src/nip11.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/buzz-relay/src/nip11.rs b/crates/buzz-relay/src/nip11.rs index e988c03f3..ca859e033 100644 --- a/crates/buzz-relay/src/nip11.rs +++ b/crates/buzz-relay/src/nip11.rs @@ -180,6 +180,25 @@ pub(crate) fn nip11_facts(state: &crate::state::AppState) -> (Option, bo (relay_self, advertise_nip43) } +/// Multi-tenant conformance static-input fence (surface row "NIP-11 relay info +/// and relay `self`"). +/// +/// The conformance obligation: `RelayInfo::build` "must not grow unscoped +/// DB/search/audit inputs", so an unauthenticated NIP-11 read can never become +/// an enumeration oracle for other communities. Today `build` takes only static +/// and scalar inputs — the per-deployment facts arrive pre-derived through +/// [`nip11_facts`], which reads config and the relay keypair only. +/// +/// This const binds `RelayInfo::build` to its **exact** allowed signature. The +/// moment someone adds a `&Db`, `&AppState`, a search handle, an audit handle, +/// or any other unscoped input, the function pointer's type stops matching and +/// **this file fails to compile** — turning a silent cross-tenant leak into a +/// hard build break, the same way a deny-lint would. If you must change this +/// signature, you are changing the conformance contract: update the conformance +/// doc and prove the new input is host-scoped, not unscoped, first. +const _RELAY_INFO_BUILD_STATIC_INPUT_FENCE: fn(Option<&str>, bool, usize) -> RelayInfo = + RelayInfo::build; + #[cfg(test)] mod tests { use super::*;