fix(sweep): keep the UI sweep away from real wallet data
The full UI sweep (dev builds only) ran its demo surfaces against the
real profile. Its teardown deleted wallet-{cold,trading,mining,donations,
2023,payroll}.dat from the real datadir, it created zero-filled wallet
files and picker-demo/extra-wallets folders, its demo wallet-index upsert
re-minted the active wallet's scopeId (hiding "w:"-scoped contacts), the
Contacts tab could re-attach legacy-scoped contacts to such a throwaway
id in addressbook.json, and it persisted the demo active wallet file and
Mining pool worker.
- Preflight: a full sweep is refused (toast + log line) while a real
workflow is in flight: a pending seed_migration_* setting or open
migrate dialog, a wallet switch, an encryption/import workflow, an
encrypt/PIN/lock operation, the seed-backup dialog, or a bootstrap.
The DRAGONX_FULL_SWEEP hook then exits as for an empty catalog.
- Persistence latches: AddressBook (snapshot on the first sweepBegin,
save/load latched while a demo-contacts surface is shown), WalletIndex
and Settings (active wallet file, contacts view mode, pool URL/worker)
for the whole sweep. Settings::sweepEnd() is the last statement of
clearDemoWalletData and flushes once only if a save was suppressed; a
quit mid-sweep writes nothing to the profile (the 0700 sandbox, with
zero-filled demo files only, may stay in the temp dir).
- Scope ids: during a full sweep activeWalletScopeId() returns the
persisted id or "" and never mints one, so the Contacts tab cannot
move legacy-scoped contacts to a throwaway id. The only address-book
write a sweep can still cause is the one-time legacy re-attach to the
wallet's persisted id, which opening Contacts does anyway.
- Sandbox: demo wallet files and folders go to a fresh random 0700 dir
under the temp dir (exclusive create, a pre-existing path or symlink
is refused), removed at sweep end. The teardown that deleted those six
wallet files is gone.
- Cleanup: the migrate-to-seed state, wizard phase, demo chat
conversations and a fabricated demo chat identity are reset at sweep
end; the seed-backup surface always shows the demo phrase and a real
z_exportmnemonic result is dropped while the sweep runs.
- i18n: one new key (screenshot_sweep_refused) in i18n.cpp and the 8
language files.
Tests: the address book snapshots only on the first sweepBegin, keeps
addressbook.json byte-identical while latched and restores the real
book; the wallet index keeps wallets.json and the real scope id, also
with the background writer attached; Settings restores the latched
values and flushes once only when a save was suppressed; the sandbox
helper refuses a pre-existing path or symlink and removes only a
prefixed dir, never a symlink's target.
Release note: v2.0.x UI sweeps could delete wallet-{cold,trading,mining,
donations,2023,payroll}.dat from the data directory and re-scope
contacts; this cannot be undone automatically.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+8
-2
@@ -1445,6 +1445,9 @@ std::string App::activeWalletScopeId()
|
||||
if (const auto* existing = wallet_index_.find(file)) {
|
||||
if (!existing->scopeId.empty()) return existing->scopeId;
|
||||
}
|
||||
// Never mint during a UI sweep: the demo index is thrown away at sweep end, and the Contacts tab
|
||||
// would re-attach legacy-scoped contacts to the throwaway id and save the address book for real.
|
||||
if (capture_mode_) return std::string();
|
||||
|
||||
// Establish one now: 16 random bytes, "w:"-prefixed so it's distinguishable from the legacy
|
||||
// 64-hex address-hash scopes we migrate away from.
|
||||
@@ -5196,9 +5199,9 @@ bool App::lockLiteWallet()
|
||||
return ok;
|
||||
}
|
||||
|
||||
void App::seedChatDemoData()
|
||||
bool App::seedChatDemoData()
|
||||
{
|
||||
if (!chat::hushChatFeatureEnabledAtBuild()) return;
|
||||
if (!chat::hushChatFeatureEnabledAtBuild()) return false;
|
||||
|
||||
// Demo identity so the tab renders the populated UI (hasIdentity()==true). Injected messages go
|
||||
// straight into the in-memory store (no DB attached here → not persisted, gone on restart).
|
||||
@@ -5209,12 +5212,14 @@ void App::seedChatDemoData()
|
||||
// and DECRYPTED as incoming. Only fabricate an identity when there is no real one.
|
||||
// 2. Use a RANDOM secret, never a fixed string. A constant demo secret is the SAME public key on
|
||||
// every install/wallet, i.e. everyone who ran the demo shared one cryptographic identity.
|
||||
bool fabricated = false;
|
||||
if (!chat_service_.hasIdentity()) {
|
||||
chat::ChatKeyPair keys;
|
||||
const std::string demoSecret = generateChatLocalId("demo-chat:", 24); // random, per-run
|
||||
if (chat::deriveChatIdentityFromSecret(demoSecret, keys).status
|
||||
== chat::ChatIdentityStatus::Ready) {
|
||||
chat_service_.setIdentity(keys);
|
||||
fabricated = true;
|
||||
}
|
||||
chat::wipeChatKeyPair(keys);
|
||||
}
|
||||
@@ -5244,6 +5249,7 @@ void App::seedChatDemoData()
|
||||
// An outgoing contact request awaiting a reply (no peer key → "waiting" composer state).
|
||||
add("demo-3", "zs1demopeerawaitingtheirfirstreplybeforewecanmessagethemyet00", "",
|
||||
chat::ChatDirection::Outgoing, chat::ChatMessageKind::ContactRequest, "hey, let's chat", 1751100000, "demo:c1");
|
||||
return fabricated;
|
||||
}
|
||||
|
||||
void App::exportAllKeys(std::function<void(const std::string&, int, int)> callback)
|
||||
|
||||
Reference in New Issue
Block a user