From 60498fecc4a2966bde8d34b1c5d33af3c0717794 Mon Sep 17 00:00:00 2001 From: Administrator Date: Wed, 11 Mar 2026 19:28:25 +0100 Subject: [PATCH] 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 --- lib/backup-crypto.js | 5 +++++ main.js | 22 ++++++++++++---------- renderer/app.js | 9 +++++++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/backup-crypto.js b/lib/backup-crypto.js index c857f93..6e18bc8 100644 --- a/lib/backup-crypto.js +++ b/lib/backup-crypto.js @@ -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); } } diff --git a/main.js b/main.js index 65bf055..84410c3 100644 --- a/main.js +++ b/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() }; }); diff --git a/renderer/app.js b/renderer/app.js index 3e5b353..f68aa2f 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -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'); }