diff --git a/docs/wallet-hardening.md b/docs/wallet-hardening.md index e48bf6a..1e24b4d 100644 --- a/docs/wallet-hardening.md +++ b/docs/wallet-hardening.md @@ -112,4 +112,5 @@ Land W7-2 first — it unblocks the rest. ## Progress log +- **P0-A / W2-1** — ☑ landed: the decrypt-wallet flow now scrubs (best-effort in-place zero-overwrite) and removes the plaintext key export (`obsidiandecryptexport…`) as soon as the `z_importwallet` attempt resolves — success or failure — so a full cleartext dump of every private key is no longer left on disk forever. Recovery remains the encrypted backup (`wallet.dat.encrypted.bak`). `app_security.cpp` (after the import call). Not unit-testable (fs I/O in a deep lambda); build-clean, `ctest` 1/1 (no regression). - **P0-A / W7-1** — ☑ landed: `RedactConsoleCommand`/`ConsoleCommandCarriesSecret` in `console_tab_helpers` redact secret-bearing commands (an allowlist of 13 first-tokens: `walletpassphrase`, `encryptwallet`, `z_importkey`, …) to `> walletpassphrase ****` before they hit the console echo AND the recall history; the real command still executes unredacted. Wired into `submitConsoleCommand` (`console_tab.cpp`). New `testConsoleSecretRedaction` (11 assertions). Clean build; `ctest` 1/1. (Output-secret commands like `z_exportkey` — result redaction — remain a follow-up.) diff --git a/src/app_security.cpp b/src/app_security.cpp index f734926..d3a9cde 100644 --- a/src/app_security.cpp +++ b/src/app_security.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -1606,6 +1608,27 @@ void App::renderDecryptWalletDialog() { WalletSecurityImportRpcAdapter importAdapter(rpc_.get(), saved_config_); auto importResult = services::WalletSecurityWorkflowExecutor::importWallet( importAdapter, exportPath); + + // The plaintext key export (obsidiandecryptexport…) has served its purpose now + // that the import attempt has resolved — scrub and remove it so a full cleartext + // dump of every private key isn't left on disk forever. Recovery, if ever needed, + // is the encrypted backup (wallet.dat.encrypted.bak), never this file. + { + std::error_code delEc; + const auto sz = std::filesystem::file_size(exportPath, delEc); + if (!delEc && sz > 0) { + std::fstream scrub(exportPath, + std::ios::binary | std::ios::in | std::ios::out); + if (scrub) { + const std::vector zeros(static_cast(sz), 0); + scrub.write(zeros.data(), static_cast(sz)); + scrub.flush(); + } + } + std::filesystem::remove(exportPath, delEc); + DEBUG_LOGF("[decrypt] removed plaintext key export after import\n"); + } + if (!importResult.ok) { std::string err = importResult.error; if (worker_) {