From 955f6871db21e934aefd4e431421c3e096c8d47e Mon Sep 17 00:00:00 2001 From: klopez4212 Date: Mon, 6 Jul 2026 09:07:42 +0100 Subject: [PATCH] fix: require NIP-98 for transcribe endpoints, allow stop during startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. P1 — Force NIP-98 signed auth for /transcribe/* endpoints regardless of BUZZ_REQUIRE_AUTH_TOKEN. The X-Pubkey dev fallback is spoofable, so a billable endpoint must always require cryptographic proof of identity before the membership check trusts the pubkey. 2. P2 — DictationButton now allows the stop action whenever isRecording is true, even during startup (mic live but SDP exchange in progress) or when the composer is disabled. Only blocks the button when idle and disabled, or when startup hasn't captured the mic yet. --- crates/buzz-relay/src/api/transcribe.rs | 9 ++++----- .../src/features/dictation/ui/DictationButton.tsx | 12 +++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/buzz-relay/src/api/transcribe.rs b/crates/buzz-relay/src/api/transcribe.rs index a7f1746da..5bba57b41 100644 --- a/crates/buzz-relay/src/api/transcribe.rs +++ b/crates/buzz-relay/src/api/transcribe.rs @@ -184,12 +184,11 @@ async fn authenticate( })?; let url = super::bridge::nip98_expected_url(&state.config.relay_url, &tenant, path); + // Always require NIP-98 signed auth for transcribe endpoints — these mint + // billable OpenAI sessions, so we cannot trust the unauthenticated X-Pubkey + // dev fallback (which is spoofable) regardless of BUZZ_REQUIRE_AUTH_TOKEN. let (pubkey, event_id_bytes) = super::bridge::verify_bridge_auth( - headers, - method, - &url, - None, - state.config.require_auth_token, + headers, method, &url, None, true, // force NIP-98 — billable endpoint )?; super::bridge::check_nip98_replay(state, &tenant, event_id_bytes).await?; diff --git a/desktop/src/features/dictation/ui/DictationButton.tsx b/desktop/src/features/dictation/ui/DictationButton.tsx index a1151bdc1..387a0ee32 100644 --- a/desktop/src/features/dictation/ui/DictationButton.tsx +++ b/desktop/src/features/dictation/ui/DictationButton.tsx @@ -28,11 +28,13 @@ export function DictationButton({ ? "Transcribing…" : "Dictate message"; - // Allow the stop action even when the composer is disabled — the user must - // always be able to stop an active recording session. Only block *starting* - // a new recording when disabled. - const isDisabled = - dictation.isStarting || (disabled && !dictation.isRecording); + // Allow the stop action whenever the mic is live (isRecording), even if + // the session setup is still in progress (isStarting) or the composer is + // disabled. Only block the button when idle + disabled, or when startup + // hasn't captured the mic yet (isStarting && !isRecording). + const isDisabled = dictation.isRecording + ? false + : disabled || dictation.isStarting; return (