mirror of
https://github.com/block/buzz.git
synced 2026-11-13 12:51:34 +00:00
**Category:** new-feature **User Impact:** Users can create, download, and verify a password-protected backup of their private identity from desktop Settings. **Problem:** Buzz does not currently give signed-in users a Settings-based path to protect or validate their private identity independently of onboarding. **Solution:** Add a focused backup menu to the private-key row, keep encryption and verification local in Rust, and preserve completed encrypted backups briefly so native saves can be retried without repeating encryption. <details> <summary>File changes</summary> **desktop/src/features/settings/** Adds the background backup lifecycle, create and test dialogs, private-key menu integration, password handling, and focused unit coverage. **desktop/src/features/onboarding/ui/NsecMaskedDisplay.tsx** Extends the masked private-key display with reusable overflow-menu actions used by Settings. **desktop/src/app/App.tsx** Mounts the backup provider at app scope so encryption and save work survive closing Settings or the modal. **desktop/src/shared/api/tauriIdentity.ts** Adds typed desktop bindings for local backup creation, save, selection, and verification. **desktop/src-tauri/src/key_backup.rs and desktop/src-tauri/src/commands/identity.rs** Implements local NIP-49 encryption, password generation, file handling, and public-identity-only verification results. **desktop/src-tauri/src/egress_guard.rs and guarded call sites** Blocks encrypted secret material from relay, websocket, snapshot, sharing, and huddle egress paths. **desktop/src-tauri tests and fixtures** Covers encryption, verification, file behavior, and fail-closed no-egress protections. **desktop/src/testing/e2eBridge.ts, desktop/tests/, and desktop/playwright.config.ts** Expands the mock native bridge and browser coverage across create, retry, expiry, and current/different-identity verification states. **desktop/src-tauri/Cargo.toml, Cargo.lock, and assets** Adds the local cryptography/password-generation dependencies and embedded short-word list. </details> ## Reproduction steps 1. Run the desktop app and open **Settings → Profile → Identity**. 2. Open the private-key overflow menu and choose **Create backup**. 3. Enter or generate a valid password, submit, and confirm progress continues if the dialog or Settings is closed. 4. Save the resulting `.ncryptsec` file; cancel and retry to confirm the temporary download remains available. 5. Choose **Test backup**, select the file, enter a wrong password, then retry with the correct password. 6. Confirm success identifies whether the backup matches the current identity and displays only the public `npub`. ## Screenshots | Settings identity | Private-key menu | Create backup | |---|---|---| | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/981e391b-6829-4081-95ca-ca75a369de71" /> | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/7972c68e-7635-47d8-b0ad-9639390d3e6c" /> | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/4709c8f7-cf02-46f1-bec9-b3f98fe56fb2" /> | | Encrypting | Download available | Test success | |---|---|---| | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/1ac3e934-2b4b-4135-bae6-126c715c8c59" /> | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/cb6f07ee-a16f-44a5-b9a0-6b9fe0e4d40d" /> | <img width="1280" height="720" alt="image" src="https://github.com/user-attachments/assets/ea58b1b1-966c-46aa-8d59-92c9f06a25bd" /> | Visual review and additional states: [Buzz thread](buzz://message?channel=50ca7ef1-201e-4159-9499-40de3964b7c3&id=87eceb5f0f82fd50c32e560de3d35be48e293760f6620718aafdcef289d475fe) --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
59 lines
2.8 KiB
Rust
59 lines
2.8 KiB
Rust
//! Relay egress guard for NIP-49 key-backup material.
|
|
//!
|
|
//! The local `ncryptsec` backup (see [`crate::key_backup`]) must NEVER be
|
|
//! transmitted to a relay. This module enforces that contract at runtime,
|
|
//! fail-closed, at every relay-bound egress boundary:
|
|
//!
|
|
//! | # | Boundary | Site |
|
|
//! |---|----------|------|
|
|
//! | 1 | `submit_signed_event_at_with_keys` (funnel for `submit_event*`) | `relay/submit.rs` |
|
|
//! | 2 | `sync_managed_agent_profile` | `relay.rs` |
|
|
//! | 3 | pre-signed path into the boundary-1 funnel | `relay/submit.rs` |
|
|
//! | 4 | `submit_signed_event_with_keys` | `relay.rs` |
|
|
//! | 5 | huddle STT publisher | `huddle/pipeline.rs` |
|
|
//! | 6 | `submit_engram_event` (team snapshot) | `commands/team_snapshot.rs` |
|
|
//! | 7 | `submit_engram_event` (persona import) | `commands/personas/snapshot/import.rs` |
|
|
//! | 8 | native websocket send loop (all webview relay WS) | `native_websocket.rs` |
|
|
//!
|
|
//! The inventory-completeness test in `egress_guard_tests.rs` asserts that
|
|
//! every `/events` URL-construction site in the tree calls this guard, so a
|
|
//! new submission path fails the build until it is wired.
|
|
//!
|
|
//! Scope: `ncryptsec1` only. The raw `nsec` intentionally transits the
|
|
//! NIP-44-encrypted pairing session (NIP-AB payload_type "nsec"); guarding it
|
|
//! here would break pairing. Raw-key DLP is separate policy work.
|
|
|
|
/// Bech32 HRP of NIP-49 encrypted secret keys.
|
|
const NCRYPTSEC_PREFIX: &str = "ncryptsec1";
|
|
/// Bech32 also permits an ALL-UPPERCASE encoding of the same payload
|
|
/// (BIP-173); an uppercased valid backup decodes identically, so the guard
|
|
/// must reject it too. Mixed case is invalid bech32 and cannot decode — a
|
|
/// substring matching either all-lower or all-upper prefix covers every
|
|
/// decodable form.
|
|
const NCRYPTSEC_PREFIX_UPPER: &str = "NCRYPTSEC1";
|
|
|
|
/// Reject `text` if it contains NIP-49 key-backup material.
|
|
///
|
|
/// Returns `Err` when an `ncryptsec1…` (or uppercase `NCRYPTSEC1…`)
|
|
/// substring is present. Callers MUST abort the network operation on `Err` —
|
|
/// this is a fail-closed guard, not a warning.
|
|
pub fn assert_no_key_backup(text: &str, context: &'static str) -> Result<(), String> {
|
|
if text.contains(NCRYPTSEC_PREFIX) || text.contains(NCRYPTSEC_PREFIX_UPPER) {
|
|
return Err(format!(
|
|
"blocked {context}: payload contains NIP-49 key-backup material \
|
|
(ncryptsec); the local key backup must never be transmitted to a relay"
|
|
));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Byte-slice variant for callers that hold serialized bodies.
|
|
pub fn assert_no_key_backup_bytes(body: &[u8], context: &'static str) -> Result<(), String> {
|
|
// ncryptsec is ASCII bech32; a UTF-8-lossy view preserves any occurrence.
|
|
assert_no_key_backup(&String::from_utf8_lossy(body), context)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "egress_guard_tests.rs"]
|
|
mod tests;
|