Skip to content

fix(identity): clear a copied private key, name restore failures, fix storage copy - #3236

Merged
loganj merged 8 commits into
mainfrom
larry/identity-minor
Nov 11, 2026
Merged

loganj merged 8 commits into
mainfrom
larry/identity-minor

Conversation

@loganj

@loganj loganj commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Clipboard: Copy private key left the nsec on the clipboard indefinitely, and in clipboard history. Now the host writes it on every platform, marked as sensitive so clipboard managers and Windows clipboard history and cloud sync skip it. 60 seconds after the latest key copy it tries to clear the clipboard, if the clipboard still holds that key. The copy message says Buzz tries to clear it.
  • Restore errors: a restore from backup reported almost every failure as "Could not restore your identity. Try again." Now a browser sign-in the user declined reads as Restore was canceled, a browser step that timed out says so, and no answer from Builderlab (network, DNS, TLS) reads as Couldn't reach Builderlab. Check your connection and try again. Other failures keep the generic text.
  • Unreadable org sign-in: a saved organization sign-in that native can't read (storage-corrupt) told the user to check credential permissions. Now it says Buzz can't read it, that signing in again may not replace it, and to contact support if it keeps happening. It doesn't suggest Sign out of Buzz: that deletes a damaged record, but not duplicate Linux keyring items, which report the same code.
  • Copy: the "a different key is already saved" refusal said "This Mac" on Windows and Linux too. It now says "this computer".

Details

  • src-tauri/src/clipboard.rs: clipboard_write_text takes an optional clear_after_secs (capped at 10 minutes). With it, the text is set with arboard's history exclusion (exclude_from_history on macOS and Linux, exclude_from_monitoring on Windows). The copied text is held in Zeroizing buffers in Buzz's code. copy_secret counts a copy only after it was written; after the delay, clear_secret clears only if no later counted copy happened and the clipboard text still equals it. Both run on the main thread with the clipboard held. No new command or permission.
  • src/shared/clipboard.ts: writeClipboardSecret uses that host path on every platform (a browser build can't read the clipboard back, so there it only copies). PrivateKey uses it for Copy private key.
  • src-tauri/src/nsec_backup.rs:
    • The OAuth callback's error=access_denied maps to CANCELED. Other error values stay RETRY.
    • The callback's own timeout passes through as oauth_callback::TIMED_OUT.
    • A new OFFLINE message covers a failed send on the code exchange, a failed send on account reads (new ReadError::Unreachable), and the backup client's Transport error. A backup pass still reports Unreachable as before.
  • src/features/federated-identity/CommunitySignIn.tsx: recoveryMessage has its own text for storage-corrupt. Other storage-* codes keep the permissions text.
  • src/features/identity/setup.ts: "This Mac" → "this computer".
  • docs/identity.md and docs/buzz-help.md describe the clipboard and restore rules.
  • Tests:
    • Native: a declined browser sign-in is CANCELED and another error is RETRY, both with no account request. An unreachable service is OFFLINE, and a backup pass still maps it to Unreachable.
    • Native (clipboard.rs, fake clipboard): a copy is cleared when its wait ends; a second copy of the same key keeps the first wait from clearing it; a failed second copy leaves the first clear in place; anything else on the clipboard is left alone.
    • The copy test runs as Linux and expects the host write with clearAfterSecs: 60.
    • CommunitySignIn shows the unreadable-sign-in text for storage-corrupt and not the permissions text.

Known limits

  • A key the user selects and copies by hand from the revealed field is not cleared, unless a Copy private key wait is still running.
  • The history exclusion is a hint. A clipboard manager that ignores it still records the key.
  • The Windows and Linux exclusion paths compile only on those platforms; CI builds them.
  • The clear runs in the app. If Buzz quits within the minute, the key stays on the clipboard. Clear failures are ignored, and another app can write between the read and the clear.
  • The import-before-link case from the same audit is not changed here. The import page's link step runs only for a signed-in account with no linked key, and no path into the import page reaches it with one. The entry points checked were the connect choice, Welcome back, the restore page and a community that isn't hosted.

🤖 Generated with Claude Code

@loganj
loganj marked this pull request as ready for review November 11, 2026 18:55
@loganj
loganj requested a review from a team as a code owner November 11, 2026 18:55
kalvinnchau

This comment was marked as resolved.

kalvinnchau

This comment was marked as resolved.

kalvinnchau

This comment was marked as resolved.

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Approved at 40e9756.

The previously reported issues are addressed: Linux expiry reads move off the GTK main thread, the active recovery screen displays the classified restore causes, and expiry work retains a SHA-256 fingerprint instead of the original copied key. The admission guard now constructs ownership only after successful acquisition, so rejected attempts cannot release another reader’s flag.

The consecutive-rejection regression was exercised with the extracted production helper: all four checks passed, including release and unwind behavior. Restoring the prior eager-construction implementation caused both rejection checks to fail. Earlier Chromium/WebKit checks covered copy interaction and restore error/retry/fallback behavior with modeled native IPC; those frontend checks were not rerun for this Rust-only repair.

Native Linux/Windows clipboard behavior and production timeout scheduling remain unverified in an isolated OS environment. A blocked dependency read can still retain one worker and partial clipboard bytes; the repair bounds additional reader admission rather than cancelling that read. No new actionable defect was established in the repair review.

loganj and others added 8 commits November 11, 2026 16:42
… storage copy

- Copy private key goes through the host clipboard on every platform,
  which clears it after 60 seconds if it still holds the key.
- Restore: a declined browser sign-in reads as canceled, a timed-out
  browser step as timed out, and no answer from Builderlab as
  "Couldn't reach Builderlab" instead of the generic retry text.
- A damaged saved org sign-in (storage-corrupt) no longer reads as a
  permissions problem.
- "This Mac" in the key-already-saved refusal now says "this computer".

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
…s clear on re-copy

- A secret copy is set with arboard's history exclusion (macOS and Linux
  clipboard managers; Windows history and cloud sync).
- Only a successfully written copy counts, and only the latest copy's
  wait may clear, so copying the key again doesn't clear it early and a
  failed copy leaves the earlier clear in place.
- The storage-corrupt text no longer suggests Sign out of Buzz:
  duplicate Linux keyring items report the same code and can't be
  deleted that way.
- Copy says Buzz tries to clear the clipboard.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
…text

The secret copy now holds the key only in Zeroizing buffers in our code.
The storage-corrupt text no longer says signing in again can't replace it,
since a post-write verify mismatch can succeed on retry. Docs say clipboard
managers skip the copy only if they honor the hint.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
After a key copy, a copy inside Buzz makes WebKitGTK the clipboard
owner. The expiry check then read the clipboard on that same main loop,
which could not answer while blocked: arboard waits up to 4 s on X11 and
without limit on Wayland. On Linux the check now reads from a worker
with its own handle and gives up after 10 s; only the clear runs on the
main thread, and it checks the generation again.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
KeyRestore, the screen a returning user sees, showed one generic line
for every error, so the canceled, timed-out and offline causes never
reached it. It now shows the native cause, then offers key entry.
Unavailable and refused recovery, and the missing- and partial-backup
copy, are unchanged.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
Review: the expiry read on Linux runs in a blocking task. When a Wayland
owner never finishes a transfer, the read never returns and the timeout
only drops the join handle, so the task kept its copy of the private key,
and each later copy could add another stuck worker.

The wait now keeps only a SHA-256 fingerprint of the key, and the read
compares against that. At most one expiry read runs at a time; while one
is stuck, later checks leave the clipboard alone, as a read that times
out already does.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
…used

Reading::begin built its guard eagerly with then_some, so a refused
admission dropped a guard whose Drop cleared the flag the running read
still held, and the next read was admitted. The guard now exists only
after the flag is taken. The test checks two refusals in a row.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
The Replace tests from #3233 read the copied key from the browser
clipboard. This branch copies a private key through the host on every
platform and clears it after a minute, so the tests now answer that
command and check the host write.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Logan Johnson <[email protected]>
@loganj
loganj force-pushed the larry/identity-minor branch from 40e9756 to 285315e Compare November 11, 2026 21:57
@loganj
loganj merged commit a774b09 into main Nov 11, 2026
66 checks passed
@loganj
loganj deleted the larry/identity-minor branch November 11, 2026 22:16
joahg added a commit to joahg/buzz-app that referenced this pull request Nov 11, 2026
* origin/main:
  fix(identity): show a backed-up key as backed up, and drop dead retries (block#3223)
  fix(browser): open Builderlab account connections in the system browser (block#3287)
  Expose exact captured-message reads (block#3264)
  docs(agents): keep internal discussion out of public repo content (block#3258)
  fix(identity): clear a copied private key, name restore failures, fix storage copy (block#3236)
  fix(channels): list workspaces that load after `#` opens (block#3277)
  feat(profiles): show a larger avatar in the hover profile preview (block#3232)
  fix(identity): ask for credentials on the next sign-in after signing out (block#3231)
  fix(relay): keep saved channels when the community refuses sign-in (block#3215)

Signed-off-by: Joah Gerstenberg <[email protected]>
joahg added a commit to joahg/buzz-app that referenced this pull request Nov 12, 2026
* origin/main: (38 commits)
  Fix reminder counts with compact dock corner tabs (block#3126)
  Add conditional undo for explicit reads (block#3291)
  fix(composer): edit channel links as atomic chips (block#3312)
  fix(agents): show remote instruction limit (block#3325)
  NEXUS-4033: Offer session skills in the core task composer (block#2790)
  feat(github): name pasted repository and commit links (block#3268)
  Let plugins give standing instructions to every agent turn (block#3285)
  fix(relay): read a channel's creation once, not per metadata version (block#3244)
  fix(messages): remove unconfirmed sends from this device (block#3317)
  fix: expose proven non-delivery for artifacts (block#3300)
  feat(plugins): let plugins mark a person's name in @mention suggestions (block#3260)
  fix(mcp): strip metadata from images agents attach (block#3311)
  fix(identity): name a linked key that isn't here, and keep Restore after a failed backup check (block#3238)
  feat(changesets): show the files a local agent changed on its reply (block#3214)
  fix(identity): show a backed-up key as backed up, and drop dead retries (block#3223)
  fix(browser): open Builderlab account connections in the system browser (block#3287)
  Expose exact captured-message reads (block#3264)
  docs(agents): keep internal discussion out of public repo content (block#3258)
  fix(identity): clear a copied private key, name restore failures, fix storage copy (block#3236)
  fix(channels): list workspaces that load after `#` opens (block#3277)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants