Release v1.4.20 with comprehensive audit fixes (140 issues) and expanded test coverage
- Speed calculation: raised minimum elapsed floor to 0.5s preventing unrealistic spikes - Reconnect: exponential backoff with consecutive counter, clock regression protection - Download engine: retry byte tracking (itemContributedBytes), mkdir before createWriteStream, content-length validation - Fire-and-forget promises: all void promises now have .catch() error handlers - Session recovery: normalize stale active statuses to queued on crash recovery, clear speedBps - Storage: config backup (.bak) before overwrite, EXDEV cross-device rename fallback with type guard - IPC security: input validation on all string/array IPC handlers, CSP headers in production - Main process: clipboard memory limit (50KB), installer timing increased to 800ms - Debrid: attribute-order-independent meta tag regex for Rapidgator filename extraction - Constants: named constants for magic numbers (MAX_MANIFEST_FILE_BYTES, MAX_LINK_ARTIFACT_BYTES, etc.) - Extractor/integrity: use shared constants, document password visibility and TOCTOU limitations - Tests: 103 tests total (55 new), covering utils, storage, integrity, cleanup, extractor, debrid, update Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
556f0672dc
commit
63fd402083
+67
-12
@@ -6,6 +6,20 @@ import { IPC_CHANNELS } from "../shared/ipc";
|
||||
import { logger } from "./logger";
|
||||
import { APP_NAME } from "./constants";
|
||||
|
||||
/* ── IPC validation helpers ────────────────────────────────────── */
|
||||
function validateString(value: unknown, name: string): string {
|
||||
if (typeof value !== "string") {
|
||||
throw new Error(`${name} muss ein String sein`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
function validateStringArray(value: unknown, name: string): string[] {
|
||||
if (!Array.isArray(value) || !value.every(v => typeof v === "string")) {
|
||||
throw new Error(`${name} muss ein String-Array sein`);
|
||||
}
|
||||
return value as string[];
|
||||
}
|
||||
|
||||
/* ── Single Instance Lock ───────────────────────────────────────── */
|
||||
const gotLock = app.requestSingleInstanceLock();
|
||||
if (!gotLock) {
|
||||
@@ -45,6 +59,19 @@ function createWindow(): BrowserWindow {
|
||||
}
|
||||
});
|
||||
|
||||
if (!isDevMode()) {
|
||||
window.webContents.session.webRequest.onHeadersReceived((details, callback) => {
|
||||
callback({
|
||||
responseHeaders: {
|
||||
...details.responseHeaders,
|
||||
"Content-Security-Policy": [
|
||||
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://api.real-debrid.com https://api.github.com https://bestdebrid.com https://api.alldebrid.com https://www.mega-debrid.eu"
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (isDevMode()) {
|
||||
void window.loadURL("http://localhost:5173");
|
||||
} else {
|
||||
@@ -96,13 +123,13 @@ function startClipboardWatcher(): void {
|
||||
if (clipboardTimer) {
|
||||
return;
|
||||
}
|
||||
lastClipboardText = clipboard.readText();
|
||||
lastClipboardText = clipboard.readText().slice(0, 50000);
|
||||
clipboardTimer = setInterval(() => {
|
||||
const text = clipboard.readText();
|
||||
if (text === lastClipboardText || !text.trim()) {
|
||||
return;
|
||||
}
|
||||
lastClipboardText = text;
|
||||
lastClipboardText = text.slice(0, 50000);
|
||||
const links = extractLinksFromText(text);
|
||||
if (links.length > 0 && mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send(IPC_CHANNELS.CLIPBOARD_DETECTED, links);
|
||||
@@ -144,7 +171,7 @@ function registerIpcHandlers(): void {
|
||||
if (result.started) {
|
||||
setTimeout(() => {
|
||||
app.quit();
|
||||
}, 350);
|
||||
}, 800);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
@@ -166,22 +193,50 @@ function registerIpcHandlers(): void {
|
||||
updateTray();
|
||||
return result;
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.ADD_LINKS, (_event: IpcMainInvokeEvent, payload: AddLinksPayload) => controller.addLinks(payload));
|
||||
ipcMain.handle(IPC_CHANNELS.ADD_LINKS, (_event: IpcMainInvokeEvent, payload: AddLinksPayload) => {
|
||||
validateString(payload?.rawText, "rawText");
|
||||
return controller.addLinks(payload);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.ADD_CONTAINERS, async (_event: IpcMainInvokeEvent, filePaths: string[]) => controller.addContainers(filePaths ?? []));
|
||||
ipcMain.handle(IPC_CHANNELS.GET_START_CONFLICTS, () => controller.getStartConflicts());
|
||||
ipcMain.handle(IPC_CHANNELS.RESOLVE_START_CONFLICT, (_event: IpcMainInvokeEvent, packageId: string, policy: "keep" | "skip" | "overwrite") =>
|
||||
controller.resolveStartConflict(packageId, policy));
|
||||
ipcMain.handle(IPC_CHANNELS.RESOLVE_START_CONFLICT, (_event: IpcMainInvokeEvent, packageId: string, policy: "keep" | "skip" | "overwrite") => {
|
||||
validateString(packageId, "packageId");
|
||||
validateString(policy, "policy");
|
||||
if (policy !== "keep" && policy !== "skip" && policy !== "overwrite") {
|
||||
throw new Error("policy muss 'keep', 'skip' oder 'overwrite' sein");
|
||||
}
|
||||
return controller.resolveStartConflict(packageId, policy);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.CLEAR_ALL, () => controller.clearAll());
|
||||
ipcMain.handle(IPC_CHANNELS.START, () => controller.start());
|
||||
ipcMain.handle(IPC_CHANNELS.STOP, () => controller.stop());
|
||||
ipcMain.handle(IPC_CHANNELS.TOGGLE_PAUSE, () => controller.togglePause());
|
||||
ipcMain.handle(IPC_CHANNELS.CANCEL_PACKAGE, (_event: IpcMainInvokeEvent, packageId: string) => controller.cancelPackage(packageId));
|
||||
ipcMain.handle(IPC_CHANNELS.RENAME_PACKAGE, (_event: IpcMainInvokeEvent, packageId: string, newName: string) => controller.renamePackage(packageId, newName));
|
||||
ipcMain.handle(IPC_CHANNELS.REORDER_PACKAGES, (_event: IpcMainInvokeEvent, packageIds: string[]) => controller.reorderPackages(packageIds));
|
||||
ipcMain.handle(IPC_CHANNELS.REMOVE_ITEM, (_event: IpcMainInvokeEvent, itemId: string) => controller.removeItem(itemId));
|
||||
ipcMain.handle(IPC_CHANNELS.TOGGLE_PACKAGE, (_event: IpcMainInvokeEvent, packageId: string) => controller.togglePackage(packageId));
|
||||
ipcMain.handle(IPC_CHANNELS.CANCEL_PACKAGE, (_event: IpcMainInvokeEvent, packageId: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
return controller.cancelPackage(packageId);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.RENAME_PACKAGE, (_event: IpcMainInvokeEvent, packageId: string, newName: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
validateString(newName, "newName");
|
||||
return controller.renamePackage(packageId, newName);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.REORDER_PACKAGES, (_event: IpcMainInvokeEvent, packageIds: string[]) => {
|
||||
validateStringArray(packageIds, "packageIds");
|
||||
return controller.reorderPackages(packageIds);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.REMOVE_ITEM, (_event: IpcMainInvokeEvent, itemId: string) => {
|
||||
validateString(itemId, "itemId");
|
||||
return controller.removeItem(itemId);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.TOGGLE_PACKAGE, (_event: IpcMainInvokeEvent, packageId: string) => {
|
||||
validateString(packageId, "packageId");
|
||||
return controller.togglePackage(packageId);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.EXPORT_QUEUE, () => controller.exportQueue());
|
||||
ipcMain.handle(IPC_CHANNELS.IMPORT_QUEUE, (_event: IpcMainInvokeEvent, json: string) => controller.importQueue(json));
|
||||
ipcMain.handle(IPC_CHANNELS.IMPORT_QUEUE, (_event: IpcMainInvokeEvent, json: string) => {
|
||||
validateString(json, "json");
|
||||
return controller.importQueue(json);
|
||||
});
|
||||
ipcMain.handle(IPC_CHANNELS.TOGGLE_CLIPBOARD, () => {
|
||||
const settings = controller.getSettings();
|
||||
const next = !settings.clipboardWatch;
|
||||
|
||||
Reference in New Issue
Block a user