fix(lite): address adversarial audit findings in session's lite work
Re-audited this session's lite-wallet changes (originally written at medium
effort) and fixed the genuine issues found:
- walletReady (open path): litelib_initialize_existing returns the bare string
"OK", which is NOT valid JSON, so the previous `json::accept(value)` check
marked a *successful* open as not-ready. Key off a non-empty success response
instead (the bridge already maps "Error:"/null to failure). Drops the now
unused nlohmann include.
- sync progress: while the detached sync thread is still running, syncDone_ is
authoritative — don't surface the backend's transient idle syncstatus
({"syncing":"false"} -> parser progress=1.0/complete=true) as a misleading
100%/done. Force complete=false and zero the bogus 1.0 in the progress model.
- per-address balance: also exclude `pending` outputs (notes/utxos from an
unconfirmed received tx) so per-address figures match confirmed/available.
- secret wiping: the settings page left the page-local request copies
(input.request.*Request.{passphrase,seedPhrase}) unwiped, and the
validation-only fallback path wiped nothing. Replace the single-path memzero
with an RAII scrubber that wipes both the UI char buffers and the request
string copies on every return path.
- concurrency: document that concurrent bridge->execute() is intentionally
unguarded — litelib serializes wallet access internally via
Arc<RwLock<LightWallet>>, so a C++ mutex is unnecessary and would defeat the
sync/syncstatus concurrency the design relies on. syncLaunched_ -> atomic.
Tests: fake backend now returns the real init shapes (seed object for
create/restore, bare "OK" for open) and a new open-path case guards the
walletReady regression. Removed an unreliable alloc==freed leak assert from the
thread-bearing controller test (kept in the thread-free bridge test). Also fixed
a stray CMake indent and removed ~220MB of untracked build/debug scratch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -236,6 +236,24 @@ static void evaluateLiteLifecycleRequestFromPageState(App* app) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Wipe ALL secret material when leaving this function, on every path (real execution,
|
||||
// validation-only fallback, or early return): the UI char buffers AND the std::string
|
||||
// copies inside `input.request.*Request`. The controller wipes its own by-value request
|
||||
// copy, but these page-local copies are separate; leaving them would defeat the wipe.
|
||||
struct LiteSecretScrubber {
|
||||
wallet::LiteWalletLifecycleUiExecutionInput& in;
|
||||
~LiteSecretScrubber() {
|
||||
sodium_memzero(s_settingsState.lite_lifecycle_passphrase,
|
||||
sizeof(s_settingsState.lite_lifecycle_passphrase));
|
||||
sodium_memzero(s_settingsState.lite_restore_seed,
|
||||
sizeof(s_settingsState.lite_restore_seed));
|
||||
wallet::secureWipeLiteSecret(in.request.createRequest.passphrase);
|
||||
wallet::secureWipeLiteSecret(in.request.openRequest.passphrase);
|
||||
wallet::secureWipeLiteSecret(in.request.restoreRequest.seedPhrase);
|
||||
wallet::secureWipeLiteSecret(in.request.restoreRequest.passphrase);
|
||||
}
|
||||
} liteSecretScrubber{input};
|
||||
|
||||
// When a linked lite backend is present, execute the operation for real through the
|
||||
// App-owned controller. Otherwise fall back to the validation-only adapter.
|
||||
if (auto* lite = app->liteWallet()) {
|
||||
@@ -252,11 +270,7 @@ static void evaluateLiteLifecycleRequestFromPageState(App* app) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Secrets have been consumed; wipe the UI buffers (and the request copies) so they
|
||||
// do not linger in memory after the attempt.
|
||||
sodium_memzero(s_settingsState.lite_lifecycle_passphrase, sizeof(s_settingsState.lite_lifecycle_passphrase));
|
||||
sodium_memzero(s_settingsState.lite_restore_seed, sizeof(s_settingsState.lite_restore_seed));
|
||||
|
||||
// (Secret wiping is handled unconditionally by liteSecretScrubber at function exit.)
|
||||
s_settingsState.lite_lifecycle_summary = result.bridgeResponseRedacted;
|
||||
if (result.walletReady) {
|
||||
s_settingsState.lite_lifecycle_status = "Wallet ready";
|
||||
|
||||
Reference in New Issue
Block a user