- Track whether the active identity is stored in the system keyring, local fallback file, environment, or ephemeral memory.
- Expose storage metadata through the Tauri identity response and shared TypeScript identity model.
- Add a responsive first information card beside the two backup actions without revealing boot-time identity creation.
- Show keychain password education only when secure storage succeeded and accurate neutral copy for fallback states.
- Extend identity persistence tests and split storage types into focused modules to preserve file-size limits.
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Conflict resolutions:
- relay.rs: took main's submit refactor (submit_signed_event_at_with_keys
funnel replaces the removed submit_signed_event); the persona flush loop
on main already publishes through the guarded funnel.
- egress_guard boundary 3 retargeted to the pre-signed funnel entry, and
the /events inventory rows updated for main's relay.rs / sharing.rs
layout (sharing.rs site is its in-file mock-relay test fixture; its
production publish goes through the guarded boundary-1 funnel).
- personas/snapshot/import.rs + e2eBridge.ts: kept both sides' additions.
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
**Category:** improvement
**User Impact:** Users can create a password-protected key backup, save
it locally, and prove that it works before finishing onboarding or
signing out.
**Problem:** Tyler's NIP-49 foundation made encrypted local backup
possible, but the end-to-end ceremony still needed clearer progression,
trustworthy verification, and a Settings treatment consistent with the
rest of Identity. **Solution:** This stack adds a guided
create/download/test flow, verifies the actual saved backup through Rust
without returning secret key material, minimizes password lifetime in
the webview, and restores warm, plain-language presentation across
onboarding and Settings.
> [!NOTE]
> This PR is intentionally stacked on #2937 (`eva/nip49-local-backup`).
Review only the 19 commits in this stack.
<details>
<summary>File changes</summary>
**desktop/src-tauri/src/commands/identity.rs**
Adds off-thread verification of NIP-49 backups and returns public
identity details only.
**desktop/src-tauri/src/commands/identity_key_backup_tests.rs**
Covers verification, identity matching, and failure behavior at the
command boundary.
**desktop/src-tauri/src/egress_guard_tests.rs**
Keeps the NIP-49 source inventory aligned with the new verification
path.
**desktop/src-tauri/src/key_backup.rs**
Supports real backup decryption for verification while zeroizing
submitted passwords.
**desktop/src-tauri/src/key_backup_tests.rs**
Exercises valid, wrong-password, damaged, and different-identity
backups.
**desktop/src-tauri/src/lib.rs**
Registers the verification command with the desktop runtime.
**desktop/src/features/communities/ui/WelcomeSetup.tsx**
Routes community onboarding through the revised backup ceremony.
**desktop/src/features/onboarding/lib/encryptedBackup.test.mjs**
Covers the hardened backup state model and password-clearing behavior.
**desktop/src/features/onboarding/lib/encryptedBackup.ts**
Models creation and verification with opaque request correlation and
short-lived passwords.
**desktop/src/features/onboarding/ui/BackupStep.tsx**
Presents the backup choice clearly and advances into the dedicated
download step.
**desktop/src/features/onboarding/ui/BackupTestFlow.tsx**
Adds the polished select-file, enter-password, verification, error, and
success experience.
**desktop/src/features/onboarding/ui/CommunityOnboardingFlow.tsx**
Connects community onboarding to the updated backup steps.
**desktop/src/features/onboarding/ui/DownloadKeyStep.tsx**
Adds the dedicated encrypted-backup download step and pre-creation skip
escape hatch.
**desktop/src/features/onboarding/ui/EncryptedBackupCreator.tsx**
Guides password generation, backup creation, native saving, safe retry,
and re-download.
**desktop/src/features/onboarding/ui/MachineOnboardingFlow.tsx**
Sequences chooser, download, exact-file test, and setup progression.
**desktop/src/features/onboarding/ui/NostrKeyImportForm.tsx**
Aligns encrypted-key import behavior with the backup flow.
**desktop/src/features/onboarding/ui/NsecMaskedDisplay.tsx**
Improves masked/revealed key presentation and safely wraps long private
keys.
**desktop/src/features/onboarding/ui/OnboardingChrome.tsx**
Supports the revised onboarding layout and transitions.
**desktop/src/features/onboarding/ui/SetupStep.tsx**
Integrates the completed backup ceremony with final setup.
**desktop/src/features/onboarding/ui/onboardingFlowSteps.test.mjs**
Updates flow-level assertions for the new step sequence.
**desktop/src/features/settings/ui/EncryptedBackupRow.tsx**
Adds sibling Create and Test rows that match the surrounding Identity
settings rhythm.
**desktop/src/features/settings/ui/ProfileSettingsCard.tsx**
Places password-backup controls in the Identity card.
**desktop/src/features/settings/ui/SignOutSection.tsx**
Uses explicit backup self-attestation plus the existing wipe phrase
without requiring a raw-key reveal first.
**desktop/src/shared/api/tauriIdentity.ts**
Exposes typed backup verification to the frontend.
**desktop/src/shared/lib/ncryptsecSourceScan.test.mjs**
Updates the frontend source allowlist for the verification path.
**desktop/src/testing/e2eBridge.ts**
Models creation, saving, and verification for browser coverage.
**desktop/tests/e2e/onboarding-backup.spec.ts**
Covers backup creation, exact-file testing, errors, retries, and
completion.
**desktop/tests/e2e/onboarding-docked-cta-screenshots.spec.ts**
Updates docked CTA visual coverage for the revised progression.
**desktop/tests/e2e/onboarding.spec.ts**
Keeps broader onboarding coverage aligned with the backup ceremony.
**desktop/tests/e2e/profile-nsec-reveal.spec.ts**
Covers Settings create/test behavior and updated success copy.
**desktop/tests/e2e/signout-confirmation.spec.ts**
Covers backup self-attestation, wipe phrase confirmation, and reset
behavior.
**desktop/tests/helpers/fileDrag.ts**
Adds reusable file-drop support for backup test coverage.
**desktop/tests/helpers/onboarding.ts**
Routes tests through chooser, download, and skip behavior consistently.
</details>
## Reproduction steps
1. Start fresh onboarding and choose the password-protected backup path.
2. Continue to **Backup your key**, create a password, save the
`.ncryptsec` file, and confirm that **Next** remains gated until testing
succeeds.
3. Select that exact file, enter its password, and verify the **Your
backup works!** success state.
4. Open **Settings → Identity** and confirm that **Create password
backup** and **Test password backup** appear as sibling rows.
5. Test a valid backup, then test wrong-password and different-identity
cases to confirm clear, non-secret-bearing outcomes.
6. Open sign out and confirm that backup self-attestation plus typing
`wipe all my data` remain required.
## Screenshots
### Onboarding flow
| 1. Creating your key | 2. Key created | 3. Create backup password |
|---|---|---|
| <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/fe5ebf60-02ad-4584-8e64-ff137bafac02"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/0b8c7c6c-2ae5-494e-bb81-0a7b47ea1b66"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/9fa8f6c0-832a-46ec-9af8-55dcae0adbb8"
/> |
| **4. Select the saved backup** | **5. Enter its password** | **6.
Verification succeeds** |
| <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/7ed1528c-6a1f-4411-bd3e-c57413320cdf"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/088ec3ed-2735-4b9a-a0c5-6d176b3f06c5"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/7081ce32-9371-46b2-9692-23ff77c39d35"
/> |
### Settings flow
| Create and Test tools | Tested backup success |
|---|---|
| <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/978ab672-5510-46d8-9116-6df4f1bda34c"
/> | <img width="1280" height="720" alt="image"
src="https://github.com/user-attachments/assets/56a1764d-3c3e-40e2-9f4b-962c5e96a36c"
/> |
## Verification
- Desktop typecheck
- Full desktop JS: 3,733/3,733
- `pnpm check`
- Rust desktop lib: 1,862 passed, 14 ignored; diagnostic: 3/3
- Focused Playwright: 16/16 with one worker
Native save/cancel remains the residual smoke-test risk: browser E2E
mocks the KDF/native save-dialog boundary, while real decrypt and
identity matching are covered in Rust.
---------
Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz>
On some Linux GPU/driver/compositor combinations, WebKitGTK's dmabuf
renderer aborts the web process during startup, so Buzz comes up with no
window at all and the user has no way to fix it. Setting
`WEBKIT_DISABLE_DMABUF_RENDERER=1` avoids the abort by falling back to
the shared-memory buffer path.
WebKit reads each of its rendering variables exactly once per process,
so the choice has to be made before anything initializes — there is no
runtime toggle and no second chance later in the same process. This
decides up front from two cheap preflight signals rather than reacting
to a crash:
- **NVIDIA GPU** — any DRM device under `/sys/class/drm` reporting PCI
vendor `0x10de`, the driver family behind most upstream reports.
- **AppImage** — the `APPIMAGE` environment variable. linuxdeploy's
AppRun hook pins `GDK_BACKEND=x11`, and the dmabuf renderer buys nothing
on that XWayland path.
Either signal disables the dmabuf renderer. Neither signal leaves the
environment untouched.
## Escape hatches
`--safe-rendering` forces the safest configuration for one launch —
`WEBKIT_DISABLE_DMABUF_RENDERER` plus `WEBKIT_DISABLE_COMPOSITING_MODE`
— for a machine neither signal recognises.
Any user assignment of a variable this module may set stands the
heuristic down **wholesale**. Presence is the test, not truthiness, so
`VAR=0` and `VAR=` both count: a user asking for the dmabuf renderer
*on* gets it, even on a machine the heuristic would have opted out.
`--safe-rendering` against such an assignment is refused with a
diagnostic naming both the assignment and the key to unset, and exits
non-zero — the flag and the environment are two incompatible answers to
one question, and neither is guessed.
## Placement
`webkit_rendering::apply()` runs at the top of `fn main()`, before
`buzz_lib::run()`. That is the only point where the process is still
single threaded with no GTK object alive, which is what makes
`std::env::set_var` sound; the module doc and the call site both say so.
The whole module is `#[cfg(target_os = "linux")]` — macOS and Windows
compile none of it.
The decision is a pure function of argv, an injected environment lookup,
and an injected DRM root, so all of it is unit-testable without mutating
the process environment.
Closes#2338. Upstream:
[tauri#9394](https://github.com/tauri-apps/tauri/issues/9394). Same
approach and same variable as
[clash-verge-rev](https://github.com/clash-verge-rev/clash-verge-rev/blob/main/src-tauri/src/utils/linux/workarounds.rs)
`workarounds.rs` and
[screenpipe](https://github.com/screenpipe/screenpipe/blob/main/apps/screenpipe-app-tauri/src-tauri/src/linux_webkit_env.rs)
`linux_webkit_env.rs`.
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
## Context
Adding an existing community by relay URL could fail with `Community
rejected: Load failed` even when its WebSocket endpoint was reachable.
The Add Community flow fetched `/api/join-policy` from the WebView, so a
relay without a matching CORS allowance blocked the policy request
before the app could join it.
## Summary
This bug fix fetches join policies through Tauri's native networking
layer for direct URL joins. Invite-code discovery, policy acceptance,
and signed invite claims remain on the WebView path so those operations
can migrate together later.
## Changes
- Uses native networking for Add Community and first-community direct
URL join-policy requests.
- Validates relay schemes, rejects URLs containing credentials, and
refuses redirects.
- Bounds declared and chunked native responses before JSON parsing.
- Preserves existing `404`, non-success status, malformed JSON, and
absent-policy behavior.
- Requires every join-policy caller to choose its transport explicitly.
Public relays using Buzz's default permissive CORS configuration are not
known to be affected.
### Related issue
Related to #2872.
### Testing
#### Reviewer-reproducible examples
End-to-end red/green requires a relay with restrictive CORS and a Buzz
identity authorized to join it.
##### Red: `main`
From a clean checkout of `main`:
```bash
. ./bin/activate-hermit
just staging
```
In Buzz Desktop:
1. Add another community so the restrictive-CORS relay can be removed.
2. Remove that relay.
3. Open Add Community and enter the relay's WebSocket URL.
4. Select Add Community.
Observed result:
```text
Community rejected: Load failed
```
##### Green: this PR
From a clean checkout of this branch:
```bash
. ./bin/activate-hermit
just staging
```
Repeat the same steps above.
Observed result:
```text
The community rejoins successfully.
```
Supporting checks:
- Six native join-policy tests, including oversized declared and chunked
responses.
- Four TypeScript API tests, including the native command contract.
- E2E build and four focused onboarding and sidebar Playwright tests.
- Full `just ci` and pre-push suites.
- Builderbot, Kalvin, and minimize-diff review fanout found no
actionable issues after the final rebase.
## Summary
- Replace all references to a specific corporate VPN product name with
generic "VPN" / "corporate VPN" / "VPN tunnel" / "VPN CLI" language
across 15 files (21 lines)
- Comment-only and doc-only changes — zero functional impact
- Keeps the OSS repo free of vendor-specific assumptions
`rg -i warp` returns zero hits after this change (excluding
`node_modules`, lockfiles).