import path from "node:path"; import { pathToFileURL } from "node:url"; import { describe, expect, it } from "vitest"; import { assertTrustedIpcSender } from "../src/main/ipc-security"; import { validateRealDebridLoginRequest } from "../src/shared/preload-api"; import { validateCollectorContainerInspectionRequest, validateCollectorInspectionRequest } from "../src/shared/collector"; function eventFor(url: string) { return { senderFrame: { url }, sender: { getURL: () => url } }; } describe("ipc-security", () => { it("accepts only opaque Real-Debrid browser account login requests", () => { expect(validateRealDebridLoginRequest({ accountId: "rdw_existing" })).toEqual({ accountId: "rdw_existing", create: false, dailyLimitBytes: 0 }); expect(validateRealDebridLoginRequest({ accountId: "rdw_reserved", create: true, dailyLimitBytes: 12_345.9 })).toEqual({ accountId: "rdw_reserved", create: true, dailyLimitBytes: 12_345 }); expect(() => validateRealDebridLoginRequest({ accountId: "../shared", create: true })).toThrow(/Account-Payload/i); expect(() => validateRealDebridLoginRequest({ accountId: "rdw_valid", create: "yes" })).toThrow(/Account-Payload/i); expect(() => validateRealDebridLoginRequest({ accountId: "rdw_valid", create: false, token: "secret" })).toThrow(/Account-Payload/i); expect(() => validateRealDebridLoginRequest({ accountId: "rdw_valid", create: true, dailyLimitBytes: -1 })).toThrow(/Account-Payload/i); expect(() => validateRealDebridLoginRequest({ accountId: "rdw_valid", create: true, dailyLimitBytes: Number.MAX_SAFE_INTEGER + 1 })).toThrow(/Account-Payload/i); expect(() => validateRealDebridLoginRequest({ accountId: "rdw_existing", dailyLimitBytes: 1 })).toThrow(/Account-Payload/i); }); it("accepts only bounded collector inspection payloads", () => { expect(validateCollectorInspectionRequest({ rawText: "https://1fichier.com/?abc12345", addedAt: 1234 })).toEqual({ rawText: "https://1fichier.com/?abc12345", addedAt: 1234 }); expect(() => validateCollectorInspectionRequest({ rawText: "x", addedAt: 1, token: "secret" })).toThrow(/Linksammler-Payload/i); expect(() => validateCollectorInspectionRequest({ rawText: "x".repeat(2_000_001), addedAt: 1 })).toThrow(/Linksammler-Payload/i); expect(() => validateCollectorInspectionRequest({ rawText: "x", addedAt: -1 })).toThrow(/Linksammler-Payload/i); expect(() => validateCollectorInspectionRequest({ rawText: "x", addedAt: Number.MAX_SAFE_INTEGER + 1 })).toThrow(/Linksammler-Payload/i); }); it("accepts only bounded absolute collector container paths", () => { expect(validateCollectorContainerInspectionRequest(["C:\\Imports\\one.dlc", "D:\\two.dlc"], 1234)).toEqual({ filePaths: ["C:\\Imports\\one.dlc", "D:\\two.dlc"], addedAt: 1234 }); expect(() => validateCollectorContainerInspectionRequest(["relative.dlc"], 1)).toThrow(/Container-Payload/i); expect(() => validateCollectorContainerInspectionRequest(["C:\\Imports\\one.txt"], 1)).toThrow(/Container-Payload/i); expect(() => validateCollectorContainerInspectionRequest(Array.from({ length: 101 }, (_unused, index) => `C:\\${index}.dlc`), 1)).toThrow(/Container-Payload/i); }); it("accepts IPC from the configured Vite development renderer origin", () => { expect(() => assertTrustedIpcSender(eventFor("http://localhost:5180/settings"), { isPackaged: false, devServerUrl: "http://localhost:5180", appPath: "C:\\Program Files\\MDD" })).not.toThrow(); }); it("rejects IPC from development-origin lookalikes", () => { expect(() => assertTrustedIpcSender(eventFor("http://localhost.evil.example:5180/settings"), { isPackaged: false, devServerUrl: "http://localhost:5180", appPath: "C:\\Program Files\\MDD" })).toThrow("IPC-Absender ist nicht vertrauenswürdig"); }); it("accepts IPC from the packaged renderer file tree", () => { const appPath = path.join("C:", "Program Files", "MDD", "resources", "app.asar"); const rendererUrl = pathToFileURL(path.join(appPath, "build", "renderer", "index.html")).toString(); expect(() => assertTrustedIpcSender(eventFor(rendererUrl), { isPackaged: true, devServerUrl: "http://localhost:5180", appPath })).not.toThrow(); }); it("accepts packaged renderer IPC despite Windows path casing differences", () => { const platform = Object.getOwnPropertyDescriptor(process, "platform"); Object.defineProperty(process, "platform", { configurable: true, value: "win32" }); try { const appPath = path.join("C:", "Program Files", "MDD", "resources", "app.asar"); const rendererUrl = "file:///c:/program%20files/mdd/resources/app.asar/build/renderer/index.html"; expect(() => assertTrustedIpcSender(eventFor(rendererUrl), { isPackaged: true, devServerUrl: "http://localhost:5180", appPath })).not.toThrow(); } finally { if (platform) { Object.defineProperty(process, "platform", platform); } } }); it("rejects IPC from local files outside the packaged renderer tree", () => { const appPath = path.join("C:", "Program Files", "MDD", "resources", "app.asar"); const attackerUrl = pathToFileURL(path.join("C:", "Users", "Public", "attacker.html")).toString(); expect(() => assertTrustedIpcSender(eventFor(attackerUrl), { isPackaged: true, devServerUrl: "http://localhost:5180", appPath })).toThrow("IPC-Absender ist nicht vertrauenswürdig"); }); it("rejects IPC when Electron provides no sender URL", () => { expect(() => assertTrustedIpcSender({ senderFrame: null, sender: { getURL: () => "" } }, { isPackaged: false, devServerUrl: "http://localhost:5180", appPath: "C:\\Program Files\\MDD" })).toThrow("IPC-Absender ist nicht vertrauenswürdig"); }); });