fix(storage): owner-only secret files + bound SQLite cache growth
- Write vault.dat atomically and 0600 (it holds the PIN-encrypted passphrase, so a world-readable copy enables an offline brute-force of the short PIN), and chmod the tx-history SQLite + its WAL/SHM sidecars to 0600 on open. - The tx-history snapshot and key-salt rows are keyed on a hash of the full address set, which changes whenever a new address is generated — orphaning the prior hash's full-history blob and salt forever. pruneOtherWallets() now drops rows for every non-live wallet hash on each save, bounding the database. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -114,25 +114,20 @@ bool SecureVault::store(const std::string& pin, const std::string& passphrase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write vault file: version + salt + nonce + ciphertext
|
||||
// Write vault file: version + salt + nonce + ciphertext.
|
||||
// Atomic + owner-only (0600): the vault holds the PIN-encrypted passphrase, so it must
|
||||
// not be world-readable (a copy enables an offline brute-force of the short PIN), and a
|
||||
// crash mid-write must not leave a truncated vault.
|
||||
std::string vaultPath = getVaultPath();
|
||||
fs::create_directories(fs::path(vaultPath).parent_path());
|
||||
std::string content;
|
||||
content.reserve(1 + sizeof(salt) + sizeof(nonce) + cipherLen);
|
||||
content.push_back(static_cast<char>(VAULT_VERSION));
|
||||
content.append(reinterpret_cast<const char*>(salt), sizeof(salt));
|
||||
content.append(reinterpret_cast<const char*>(nonce), sizeof(nonce));
|
||||
content.append(reinterpret_cast<const char*>(ciphertext.data()), cipherLen);
|
||||
|
||||
std::ofstream out(vaultPath, std::ios::binary);
|
||||
if (!out.is_open()) {
|
||||
DEBUG_LOGF("[SecureVault] Cannot create vault file: %s\n", vaultPath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
out.write(reinterpret_cast<const char*>(&VAULT_VERSION), 1);
|
||||
out.write(reinterpret_cast<const char*>(salt), sizeof(salt));
|
||||
out.write(reinterpret_cast<const char*>(nonce), sizeof(nonce));
|
||||
out.write(reinterpret_cast<const char*>(ciphertext.data()), cipherLen);
|
||||
out.close();
|
||||
|
||||
if (!out.good()) {
|
||||
if (!Platform::writeFileAtomically(vaultPath, content, /*restrictPermissions=*/true)) {
|
||||
DEBUG_LOGF("[SecureVault] Write failed\n");
|
||||
fs::remove(vaultPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user