fix: multiple backup import issues found in code review
- Single atomic write instead of two-phase (prevents split state on crash) - Timestamped pre-import backup (multiple imports don't overwrite safety net) - Fix UI refresh: correct function names + refresh globalSettings/alwaysOnTop - Zero sensitive buffers (key, plaintext, decrypted) after use Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fb4dd94827
commit
60498fecc4
@ -27,6 +27,8 @@ function encrypt(config, password) {
|
||||
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
||||
const tag = cipher.getAuthTag();
|
||||
|
||||
plaintext.fill(0);
|
||||
key.fill(0);
|
||||
return Buffer.concat([MAGIC, salt, iv, tag, encrypted]);
|
||||
}
|
||||
|
||||
@ -65,6 +67,9 @@ function decrypt(buffer, password) {
|
||||
return JSON.parse(decrypted.toString('utf-8'));
|
||||
} catch {
|
||||
throw new Error('Entschlüsselte Daten sind kein gültiges JSON');
|
||||
} finally {
|
||||
decrypted.fill(0);
|
||||
key.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
main.js
22
main.js
@ -649,17 +649,19 @@ ipcMain.handle('import-backup', async (_event, password) => {
|
||||
});
|
||||
if (canceled || !filePaths.length) return { ok: false, canceled: true };
|
||||
const buffer = fs.readFileSync(filePaths[0]);
|
||||
const config = backupCrypto.decrypt(buffer, password);
|
||||
// Safety net: save current config before overwriting
|
||||
const preImportPath = configStore.filePath + '.pre-import.json';
|
||||
const imported = backupCrypto.decrypt(buffer, password);
|
||||
// Safety net: timestamped backup so multiple imports don't overwrite each other
|
||||
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const preImportPath = configStore.filePath.replace('.json', `.pre-import-${ts}.json`);
|
||||
try { fs.copyFileSync(configStore.filePath, preImportPath); } catch {}
|
||||
await configStore.save(config);
|
||||
if (config.history) {
|
||||
// Overwrite history too (save() doesn't touch history)
|
||||
const full = configStore.load();
|
||||
full.history = config.history;
|
||||
await configStore._atomicWrite(JSON.stringify(full, null, 2));
|
||||
}
|
||||
// Single atomic write — no split state, no TOCTOU race
|
||||
const merged = {
|
||||
hosters: imported.hosters,
|
||||
hosterSettings: imported.hosterSettings,
|
||||
globalSettings: imported.globalSettings,
|
||||
history: imported.history || []
|
||||
};
|
||||
await configStore._atomicWrite(JSON.stringify(merged, null, 2));
|
||||
return { ok: true, config: configStore.load() };
|
||||
});
|
||||
|
||||
|
||||
@ -857,9 +857,14 @@ async function confirmBackupAction() {
|
||||
if (result.ok) {
|
||||
config = result.config;
|
||||
hosterSettings = config.hosterSettings || {};
|
||||
// Refresh global settings state (always-on-top, etc.)
|
||||
alwaysOnTopState = !!(config.globalSettings && config.globalSettings.alwaysOnTop);
|
||||
window.api.setAlwaysOnTop(alwaysOnTopState);
|
||||
closeBackupModal();
|
||||
renderSettingsPanel();
|
||||
renderAccountsList();
|
||||
renderSettings();
|
||||
renderAccounts();
|
||||
renderHosterSummary();
|
||||
renderHosterModal();
|
||||
loadHistory();
|
||||
showCopyToast('Backup importiert');
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user