Harden audit findings: correctness, type-safety, Windows conventions & polish
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified — typecheck + 234 tests + eslint + prettier green). Correctness / bugs: - B3: match the release checksum to the asset's filename line (no wrong-hash verify) - B4: newline-safe metadata probe (one --print with a unit-separator delimiter) - B5 / L88: guard the meta event against canceled items; progress no longer promotes a queued item outside pump() - B7: cookie-login promise always resolves (handles destroy-without-close) - L146: trim parser rejects >2 colon-group times; M36: Library selection counts only actionable rows - L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message, schedule picker min, dead-code/comment cleanup Type safety: - Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed) Resilience / Windows / metadata: - R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth) - W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window; L147 dead macOS branches removed - CL1: shared stdout markers; package/builder metadata (license, homepage, repository, copyright, tsbuildinfo glob) Copy / docs / tests: - M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled - New unit tests for L35 (isValidMediaItem) and L36 (compareVersions) This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard work it builds on: background running + auto-download, library clipboard detection, tray, binary management & library scale, credential encryption at rest, the shared jsonStore and ui/ primitives, and the eslint/prettier tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+17
-12
@@ -1,14 +1,9 @@
|
||||
import { dialog, type BrowserWindow } from 'electron'
|
||||
import { readFileSync, writeFileSync } from 'fs'
|
||||
import { getSettings, setSettings } from './settings'
|
||||
import { getSettings, setSettings, SECRET_KEYS } from './settings'
|
||||
import { listTemplates, replaceTemplates } from './templates'
|
||||
import { isTemplateLike } from './validation'
|
||||
import type {
|
||||
BackupExportResult,
|
||||
BackupImportResult,
|
||||
Settings,
|
||||
CommandTemplate
|
||||
} from '@shared/ipc'
|
||||
import type { BackupExportResult, BackupImportResult, Settings, CommandTemplate } from '@shared/ipc'
|
||||
|
||||
interface BackupFile {
|
||||
version: 1
|
||||
@@ -22,7 +17,12 @@ export async function exportBackup(win: BrowserWindow | undefined): Promise<Back
|
||||
filters: [{ name: 'JSON', extensions: ['json'] }]
|
||||
})
|
||||
if (res.canceled || !res.filePath) return { ok: false }
|
||||
const payload: BackupFile = { version: 1, settings: getSettings(), templates: listTemplates() }
|
||||
// Strip credentials (proxy creds, API tokens) so a backup file shared or synced
|
||||
// to the cloud doesn't leak secrets in plaintext (M22). The user re-enters them
|
||||
// after import. Shares SECRET_KEYS with settings.ts so the lists can't drift.
|
||||
const settings = { ...getSettings() }
|
||||
for (const key of SECRET_KEYS) (settings as Record<string, unknown>)[key] = ''
|
||||
const payload: BackupFile = { version: 1, settings, templates: listTemplates() }
|
||||
try {
|
||||
writeFileSync(res.filePath, JSON.stringify(payload, null, 2))
|
||||
return { ok: true, path: res.filePath }
|
||||
@@ -85,8 +85,7 @@ export async function importBackup(win: BrowserWindow | undefined): Promise<Back
|
||||
return `• ${name}: ${args}`
|
||||
})
|
||||
.join('\n')
|
||||
const more =
|
||||
commandTemplates.length > 10 ? `\n…and ${commandTemplates.length - 10} more.` : ''
|
||||
const more = commandTemplates.length > 10 ? `\n…and ${commandTemplates.length - 10} more.` : ''
|
||||
const choice = await dialog.showMessageBox(win!, {
|
||||
type: 'warning',
|
||||
buttons: ['Enable custom commands', 'Import without enabling', 'Cancel'],
|
||||
@@ -106,11 +105,17 @@ export async function importBackup(win: BrowserWindow | undefined): Promise<Back
|
||||
}
|
||||
|
||||
if (incomingSettings) {
|
||||
// Never restore credential fields from a backup. Exports strip them (M22), so an
|
||||
// imported '' would otherwise wipe a proxy/token already configured on this
|
||||
// machine; honoring a hand-edited one would reintroduce the leak vector. Either
|
||||
// way, import leaves the user's existing secrets untouched — they re-enter as needed.
|
||||
const restored: Partial<Settings> = { ...incomingSettings }
|
||||
for (const key of SECRET_KEYS) delete restored[key]
|
||||
// When the user declined to enable custom commands (or there were none to
|
||||
// enable), force the toggle off so an imported defaultTemplateId can't auto-run.
|
||||
const safeSettings = applyCustomCommands
|
||||
? incomingSettings
|
||||
: { ...incomingSettings, customCommandEnabled: false }
|
||||
? restored
|
||||
: { ...restored, customCommandEnabled: false }
|
||||
setSettings(safeSettings)
|
||||
}
|
||||
if (incomingTemplates.length > 0 || Array.isArray(file.templates)) {
|
||||
|
||||
Reference in New Issue
Block a user