fix(security): don't silently leave a wallet unencrypted or unlocked (W2-2, W2-4)
P0-B encryption-integrity cluster.
W2-2: the first-run wizard's "encrypt" stored the passphrase only in memory and let the
user into the app immediately, so a quit/crash or a failed daemon connect before the
deferred encryption applied left the wallet unencrypted with NO record encryption was
ever requested — the user believing it was encrypted. A persisted encryption_pending
settings flag is now set the moment encryption is requested (never the passphrase, only
the fact). refreshWalletEncryptionState() reconciles it on every connect: wallet observed
encrypted -> clear the flag; wallet NOT encrypted while the flag is set and no deferred
encryption is pending/in-flight -> a once-per-session "your wallet is NOT encrypted — open
Settings to finish" warning (the flag stays set, so it recurs each launch until resolved).
The passphrase is deliberately never persisted to auto-complete — surfacing it is the
secure choice.
W2-4: lockWallet()'s continuation only handled success — a failed walletlock RPC silently
left the wallet UNLOCKED (an unfulfilled auto-lock). It now logs and warns once (reset on
the next successful lock) so a failing auto-lock is visible instead of leaving the wallet
exposed.
Touches settings.{h,cpp}, app_wizard.cpp, app_security.cpp, app.h. Not unit-testable at
this layer (RPC/connect-driven state). Build-clean; ctest 1/1. See docs/wallet-hardening.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -487,7 +487,17 @@ void App::lockWallet() {
|
||||
state_.locked = true;
|
||||
state_.unlocked_until = 0;
|
||||
resetTransactionHistoryCacheSession();
|
||||
lock_failure_warned_ = false;
|
||||
DEBUG_LOGF("[App] Wallet locked\n");
|
||||
} else {
|
||||
// The walletlock RPC failed — the wallet is still UNLOCKED. Surface it (once) rather
|
||||
// than silently leaving an auto-lock unfulfilled and the wallet exposed (W2-4).
|
||||
DEBUG_LOGF("[App] walletlock failed — wallet remains unlocked\n");
|
||||
if (!lock_failure_warned_) {
|
||||
lock_failure_warned_ = true;
|
||||
ui::Notifications::instance().warning(
|
||||
"Couldn't lock the wallet — it is still unlocked. Check the daemon connection.", 12.0f);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -564,6 +574,12 @@ void App::refreshWalletEncryptionState() {
|
||||
state_.unlocked_until = until;
|
||||
state_.locked = (until == 0);
|
||||
state_.encryption_state_known = true;
|
||||
// Wallet is encrypted — any pending deferred-encryption request has now been
|
||||
// satisfied (however it completed). Clear the persisted flag (W2-2).
|
||||
if (settings_ && settings_->getEncryptionPending()) {
|
||||
settings_->setEncryptionPending(false);
|
||||
settings_->save();
|
||||
}
|
||||
if (state_.locked) {
|
||||
resetTransactionHistoryCacheSession();
|
||||
} else if (state_.transactions.empty()) {
|
||||
@@ -576,6 +592,19 @@ void App::refreshWalletEncryptionState() {
|
||||
state_.locked = false;
|
||||
state_.unlocked_until = 0;
|
||||
state_.encryption_state_known = true;
|
||||
// W2-2: encryption was requested (persisted flag) but the wallet is NOT encrypted,
|
||||
// and no deferred encryption is pending/in-flight — it was lost to a quit/crash or a
|
||||
// failed connect before it applied. Warn (once/session) instead of silently leaving
|
||||
// an unencrypted wallet the user believes is protected. The flag stays set until the
|
||||
// wallet is actually encrypted, so the warning recurs each launch until resolved.
|
||||
if (settings_ && settings_->getEncryptionPending() &&
|
||||
!wallet_security_.hasDeferredEncryption() && !encrypt_in_progress_ &&
|
||||
!encryption_incomplete_warned_) {
|
||||
encryption_incomplete_warned_ = true;
|
||||
ui::Notifications::instance().warning(
|
||||
"Wallet encryption did not complete — your wallet is NOT encrypted. "
|
||||
"Open Settings to finish encrypting it.", 30.0f);
|
||||
}
|
||||
if (state_.transactions.empty()) {
|
||||
loadTransactionHistoryCacheIfAvailable();
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user