block/buzz · error
nip98 mode always resolves principal
Error message
nip98 mode always resolves principal
What it means
Invariant panic in the admin API probe handler: when `config.auth` is `AdminAuth::Nip98`, the code assumes `authorize` already returned `Ok(Some(principal))` (nip98 always resolves a principal), so `principal.as_ref().expect("nip98 mode always resolves principal")` must be Some. It panics if the request reached the nip98 branch with `principal == None`, meaning the auth invariant was violated upstream.
Source
Thrown at crates/buzz-relay/src/api/admin/mod.rs:167
) -> Result<Json<ProbeResponse>, ApiError> {
let principal = authorize(
&state,
&headers,
uri.path_and_query()
.map_or_else(|| uri.path(), |pq| pq.as_str()),
"GET",
None,
)
.await?;
let (auth_mode, role, source, can_act, can_staff) = match &state.config.admin {
Some(config) => match &config.auth {
crate::config::AdminAuth::Disabled => ("disabled", None, None, false, false),
crate::config::AdminAuth::Nip98 => {
// principal is Some in nip98 mode (authorize returns Ok(Some(_)))
let p = principal
.as_ref()
.expect("nip98 mode always resolves principal");
let can_staff = p.role == AdminRole::Operator;
(
"nip98",
Some(admin_role_str(p.role)),
Some(admin_source_str(&p.source)),
true, // both Operator and Moderator can act
can_staff,
)
}
},
None => return Err(ApiError::not_found()),
};
Ok(Json(ProbeResponse {
status: "ok",
auth_mode,
role,
source,View on GitHub (pinned to dad5a33865)
Solutions
- Inspect how `principal` is populated before this match — ensure the nip98 authorize() path rejects (not passes) requests with no resolvable principal.
- Confirm the auth middleware runs before the probe handler and stores the principal for nip98 mode.
- Validate the NIP-98 Authorization header (base64 event, kind 27235, valid sig, correct URL/method tags) on the client side.
- Replace the expect with an explicit error response (401) if None is a legitimate runtime state rather than an invariant.
Example fix
// before
let p = principal.as_ref().expect("nip98 mode always resolves principal");
// after
let Some(p) = principal.as_ref() else {
return StatusCode::UNAUTHORIZED.into_response(); // principal must exist in nip98 mode
}; Defensive patterns
Strategy: type-guard
Validate before calling
// client-side: send a valid NIP-98 header before calling admin probe
let event = EventBuilder::auth(Url::parse("https://relay/api/admin/probe")?)
.sign_with_keys(&user_keys)?;
let header = format!("Nostr {}", URL_SAFE_NO_PAD.encode(event.as_json().to_string()));
assert!(!header.is_empty(), "NIP-98 Authorization header required for nip98 admin mode"); Type guard
fn nip98_principal(principal: Option<&AdminPrincipal>) -> Result<&AdminPrincipal, StatusCode> {
principal.ok_or(StatusCode::UNAUTHORIZED)
} Try / catch
let Some(p) = principal.as_ref() else {
return StatusCode::UNAUTHORIZED.into_response();
}; Prevention
- Always send a valid NIP-98 Authorization header in nip98 admin mode.
- Run authorize() before any handler that assumes a principal exists.
- Prefer explicit 401 over expect() when None is a reachable runtime state.
- Cover the 'no header' path with a test so the invariant stays true.
When it happens
Trigger: A request routed to the nip98 branch without a resolved principal — e.g. authorize() returned Ok(None) for a missing/invalid NIP-98 Authorization header but the caller treated it as authenticated, auth middleware ordering changed so probe runs before authorization, or an AdminAuth enum refactor altered which branch executes.
Common situations: Deployments where the relay's admin auth config changed from Disabled to Nip98 but requests carry no Authorization header; middleware refactors that make principal resolution conditional; misconfigured NIP-98 event payloads failing to parse silently upstream.
Related errors
- failed to install rustls crypto provider
- bundled model-capabilities.json failed validation: {e}
- bundled model-capabilities.json must parse
- Databricks v2 model discovery failed: workspace endpoint cat
- metrics exporter must install exactly once: {error}
AI-assisted analysis of block/buzz@dad5a33865 (2026-08-30).
Data as JSON: /api/errors/a69ba2113050d4a5.
Report an issue: GitHub.