You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Hardening and test-coverage findings in keep-desktop.
Read this before picking anything up. The original list held twenty-seven entries and far fewer distinct defects. A manual pass on 2026-07-30 read the code for each: five clusters covering twelve entries were the same defect filed repeatedly, four entries were wrong about the code, and one would have caused harm if implemented as written. Deduplicated and corrected below.
Entries here name where the code looks wrong. They are not instructions. Read the code before acting on one, and if it does not say what the entry says, correct the entry.
Open
Coordination handlers untested — Descriptor validation itself has four tests covering a valid pair and malformed external, internal and network inputs. The two coordination handlers, the one that contributes an extended public key and the one that completes and stores, have none. (Narrowed from an entry claiming descriptor coordination was untested outright.)
Auto-contribution sends an xpub without asking — The contribution handler derives and contributes the account xpub on request with no user approval step.
Connected status shown when no peers were found — The connect loop breaks on its timeout returning success, and the result handler maps that to connected.
block_in_place inside pre_sign risks deadlock — Awaits UI approval from inside the node's async handler.
Secrets travel through a cloneable message enum — Two entries, one for the export payload and one for the enum generally. Zeroizing values are carried through a Clone message type, so a clone outlives the zeroizing wrapper's guarantee.
Passphrase scoring does not detect repetition — Reworded rather than closed. The linear length-plus-variety formula both original entries described is gone; scoring now estimates the alphabet the passphrase draws from. That still does not deliver what the entries wanted, because alphabet-based entropy cannot see repetition, so a long repeated string still rates well. The defect stands; anyone verifying against the formula in the old text would have wrongly closed it.
Import share data is rendered unmasked — The import field masks only when the mode is a private key; pasted share export data renders in the clear.
Text input creates a non-zeroizing intermediate — The input callback receives a plain String before wrapping it.
Clipboard write copies the export into a non-zeroized string — The share copy path clones the value into a plain String for the clipboard.
Toast pushes content instead of overlaying — Rendered as a column above the screen.
take() leaves the vault handle empty during async work — Concurrent dispatch during that window fails silently.
npub appears in debug logs — A message enum deriving Debug prints the full npub, inconsistent with the hashing applied elsewhere.
QR payload is not zeroized, and cannot be from here — Real and not locally fixable. The widget library stores its module matrix in a private field with no accessor and no zeroizing wrapper, so dropping it releases the buffer uncleared. Closing this needs upstream support, or not retaining the type and rebuilding it every frame, which trades one persistent buffer for a stream of transient ones. Bounded: the buffer holds the passphrase-encrypted export, not key material, and the source string beside it is already wiped on reset.
Removed as already fixed
Locking now removes the on-disk active-share pointer before dropping the master key, with the ordering documented at the call site because clearing it requires an unlocked vault.
Removed as wrong
Export display shows the share in plaintext (three entries). The value is the passphrase-encrypted export, so what is on screen is ciphertext, not key material. More decisively, the same screen renders a QR code of the whole value directly above that text by design, because scanning it is how the share reaches the phone. Hiding eighty characters of a kilobyte-long ciphertext while a scannable encoding of all of it sits above protects nothing. One narrow note worth keeping: the truncation only applies above eighty characters, so a shorter value would render in full. That cannot happen for a share export, but it matters if that screen is reused for a shorter secret.
Unmapped error logging may expose file paths (two entries). The storage error type carries a path on several variants and deliberately leaves it out of its display formatting; the file-not-found, already-exists and permission-denied variants render as a code plus a generic phrase. Every variant was checked. The catch-all logs a code and a category, never a location.
friendly_err leaks invalid-input content to the interface. This is the one case where passing the detail through is correct. The catch-all returns a generic string and logs the detail, which is the right split. Invalid-input messages are validation text such as an unrecognised bech32 string; suppressing them leaves the user with a refusal and no way to tell what they typed wrong. Checked for any carrying a path or secret; there are none.
Copying an npub does not clear the clipboard. An npub is a public identifier, published on relays and handed to every app the user connects. Clearing it after a timeout breaks the ordinary reason for copying it and protects nothing. Our own timer sets a flag named for holding a secret and takes its duration from a secret-clearing setting, so routing a public value through it would misreport what the clipboard holds. Another established signer implementation draws the same line, using its sensitive-clipboard helper for the private key and seed words only, with public keys on the plain path.
Tracing subscriber logs without filtering. The environment-filter feature is enabled for the workspace, so the default initialiser reads the standard log-level variable and defaults to info. What actually differs from the command-line binary is that it writes to standard output rather than standard error and sets no explicit fallback, which is a smaller and different point.
Removed as harmful
No minimum passphrase length enforced on import. Do not implement this. Import does not choose a passphrase; it supplies one that must match what an export already used. A length rule there is not a policy, it is a filter on values the user may legitimately have to enter, and anyone holding a share exported under a shorter passphrase would find it unimportable, with the failure appearing as a validation refusal rather than anything explaining their data is fine and the rule is new. Locking a user out of their own share is worse than the weak passphrase it would discourage. The rule belongs where the passphrase is chosen and is already there: the export screen enforces a minimum before proceeding and requires the confirmation field to match.
Hardening and test-coverage findings in
keep-desktop.Read this before picking anything up. The original list held twenty-seven entries and far fewer distinct defects. A manual pass on 2026-07-30 read the code for each: five clusters covering twelve entries were the same defect filed repeatedly, four entries were wrong about the code, and one would have caused harm if implemented as written. Deduplicated and corrected below.
Entries here name where the code looks wrong. They are not instructions. Read the code before acting on one, and if it does not say what the entry says, correct the entry.
Open
block_in_placeinsidepre_signrisks deadlock — Awaits UI approval from inside the node's async handler.Clonemessage type, so a clone outlives the zeroizing wrapper's guarantee.Stringbefore wrapping it.Stringfor the clipboard.take()leaves the vault handle empty during async work — Concurrent dispatch during that window fails silently.Debugprints the full npub, inconsistent with the hashing applied elsewhere.Removed as already fixed
Locking now removes the on-disk active-share pointer before dropping the master key, with the ordering documented at the call site because clearing it requires an unlocked vault.
Removed as wrong
Export display shows the share in plaintext (three entries). The value is the passphrase-encrypted export, so what is on screen is ciphertext, not key material. More decisively, the same screen renders a QR code of the whole value directly above that text by design, because scanning it is how the share reaches the phone. Hiding eighty characters of a kilobyte-long ciphertext while a scannable encoding of all of it sits above protects nothing. One narrow note worth keeping: the truncation only applies above eighty characters, so a shorter value would render in full. That cannot happen for a share export, but it matters if that screen is reused for a shorter secret.
Unmapped error logging may expose file paths (two entries). The storage error type carries a path on several variants and deliberately leaves it out of its display formatting; the file-not-found, already-exists and permission-denied variants render as a code plus a generic phrase. Every variant was checked. The catch-all logs a code and a category, never a location.
friendly_errleaks invalid-input content to the interface. This is the one case where passing the detail through is correct. The catch-all returns a generic string and logs the detail, which is the right split. Invalid-input messages are validation text such as an unrecognised bech32 string; suppressing them leaves the user with a refusal and no way to tell what they typed wrong. Checked for any carrying a path or secret; there are none.Copying an npub does not clear the clipboard. An npub is a public identifier, published on relays and handed to every app the user connects. Clearing it after a timeout breaks the ordinary reason for copying it and protects nothing. Our own timer sets a flag named for holding a secret and takes its duration from a secret-clearing setting, so routing a public value through it would misreport what the clipboard holds. Another established signer implementation draws the same line, using its sensitive-clipboard helper for the private key and seed words only, with public keys on the plain path.
Tracing subscriber logs without filtering. The environment-filter feature is enabled for the workspace, so the default initialiser reads the standard log-level variable and defaults to info. What actually differs from the command-line binary is that it writes to standard output rather than standard error and sets no explicit fallback, which is a smaller and different point.
Removed as harmful
No minimum passphrase length enforced on import. Do not implement this. Import does not choose a passphrase; it supplies one that must match what an export already used. A length rule there is not a policy, it is a filter on values the user may legitimately have to enter, and anyone holding a share exported under a shorter passphrase would find it unimportable, with the failure appearing as a validation refusal rather than anything explaining their data is fine and the rule is new. Locking a user out of their own share is worse than the weak passphrase it would discourage. The rule belongs where the passphrase is chosen and is already there: the export screen enforces a minimum before proceeding and requires the confirmation field to match.