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:
Administrator 2026-03-11 19:28:25 +01:00
parent fb4dd94827
commit 60498fecc4
3 changed files with 24 additions and 12 deletions

View File

@ -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
View File

@ -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() };
});

View File

@ -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');
}