mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-21 22:52:19 +03:00
## Security Fixes - Sanitize OMNIROUTE_MEMORY_MB with parseInt + range validation (64-16384) to prevent command injection via spawn() args - Validate URL protocol in shell.openExternal (http/https only) to prevent RCE in Electron renderer compromise - Bump default memory from 256MB to 512MB ## Electron package-lock.json - Added to .gitignore (5278 lines removed from tracking) ## Test Suite (64 tests, 9 suites) - electron-main.test.mjs: URL validation, IPC channels, window handler - electron-preload.test.mjs: channel whitelist, API surface, open-external - cli-memory.test.mjs: injection prevention, boundary values, .env parsing ## Documentation - Desktop App section added to all 30 READMEs (9 fully translated) - USER_GUIDE.md updated with 512MB default - .env.example reflects new defaults
167 lines
4.7 KiB
JavaScript
167 lines
4.7 KiB
JavaScript
/**
|
|
* Tests for Electron preload script (electron/preload.js)
|
|
*
|
|
* Tests cover:
|
|
* - Channel whitelist validation
|
|
* - API surface correctness
|
|
* - Security boundary enforcement
|
|
*/
|
|
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
// ─── Channel Whitelist Tests ─────────────────────────────────
|
|
|
|
describe("Preload Channel Whitelist", () => {
|
|
const VALID_CHANNELS = {
|
|
invoke: ["get-app-info", "open-external", "get-data-dir", "restart-server"],
|
|
send: ["window-minimize", "window-maximize", "window-close"],
|
|
receive: ["server-status", "port-changed"],
|
|
};
|
|
|
|
function isValidChannel(channel, type) {
|
|
return VALID_CHANNELS[type]?.includes(channel) ?? false;
|
|
}
|
|
|
|
it("should have exactly 4 invoke channels", () => {
|
|
assert.equal(VALID_CHANNELS.invoke.length, 4);
|
|
});
|
|
|
|
it("should have exactly 3 send channels", () => {
|
|
assert.equal(VALID_CHANNELS.send.length, 3);
|
|
});
|
|
|
|
it("should have exactly 2 receive channels", () => {
|
|
assert.equal(VALID_CHANNELS.receive.length, 2);
|
|
});
|
|
|
|
it("should not allow crossing channel types", () => {
|
|
// Invoke channels should not be valid as send
|
|
for (const ch of VALID_CHANNELS.invoke) {
|
|
assert.equal(isValidChannel(ch, "send"), false, `${ch} should not be valid as send`);
|
|
}
|
|
// Send channels should not be valid as invoke
|
|
for (const ch of VALID_CHANNELS.send) {
|
|
assert.equal(isValidChannel(ch, "invoke"), false, `${ch} should not be valid as invoke`);
|
|
}
|
|
});
|
|
|
|
it("should reject null/undefined channels", () => {
|
|
assert.equal(isValidChannel(null, "invoke"), false);
|
|
assert.equal(isValidChannel(undefined, "invoke"), false);
|
|
});
|
|
});
|
|
|
|
// ─── API Surface Tests ───────────────────────────────────────
|
|
|
|
describe("Preload API Surface", () => {
|
|
const EXPECTED_API_METHODS = [
|
|
"getAppInfo",
|
|
"openExternal",
|
|
"getDataDir",
|
|
"restartServer",
|
|
"minimizeWindow",
|
|
"maximizeWindow",
|
|
"closeWindow",
|
|
"onServerStatus",
|
|
"removeServerStatusListener",
|
|
"onPortChanged",
|
|
"removePortChangedListener",
|
|
];
|
|
|
|
const EXPECTED_API_PROPERTIES = ["isElectron", "platform"];
|
|
|
|
it("should define all expected method names", () => {
|
|
// The preload script should expose these methods
|
|
for (const method of EXPECTED_API_METHODS) {
|
|
assert.ok(
|
|
typeof method === "string" && method.length > 0,
|
|
`Method ${method} should be valid`
|
|
);
|
|
}
|
|
});
|
|
|
|
it("should define expected property names", () => {
|
|
for (const prop of EXPECTED_API_PROPERTIES) {
|
|
assert.ok(typeof prop === "string" && prop.length > 0, `Property ${prop} should be valid`);
|
|
}
|
|
});
|
|
|
|
it("should have correct total API surface (13 items)", () => {
|
|
const totalApi = EXPECTED_API_METHODS.length + EXPECTED_API_PROPERTIES.length;
|
|
assert.equal(totalApi, 13);
|
|
});
|
|
|
|
it("should not expose any Node.js internals", () => {
|
|
const DANGEROUS_APIS = [
|
|
"require",
|
|
"process",
|
|
"child_process",
|
|
"fs",
|
|
"exec",
|
|
"spawn",
|
|
"eval",
|
|
"__dirname",
|
|
"__filename",
|
|
];
|
|
|
|
// None of these should be in the API surface
|
|
for (const api of DANGEROUS_APIS) {
|
|
assert.ok(
|
|
!EXPECTED_API_METHODS.includes(api) && !EXPECTED_API_PROPERTIES.includes(api),
|
|
`Dangerous API '${api}' should NOT be exposed`
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ─── Open External URL Validation Tests ──────────────────────
|
|
|
|
describe("Preload openExternal Security", () => {
|
|
/**
|
|
* Simulate the preload validation before invoking open-external
|
|
*/
|
|
function validateBeforeOpen(url) {
|
|
try {
|
|
const parsed = new URL(url);
|
|
return ["http:", "https:"].includes(parsed.protocol);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const SAFE_URLS = [
|
|
"https://github.com",
|
|
"http://localhost:20128",
|
|
"https://omniroute.dev/docs",
|
|
"https://example.com/path?q=1&p=2#section",
|
|
];
|
|
|
|
const DANGEROUS_URLS = [
|
|
"file:///etc/passwd",
|
|
"file:///C:/Windows/System32",
|
|
"javascript:alert(document.cookie)",
|
|
"vscode://extensions",
|
|
"data:text/html,<h1>pwned</h1>",
|
|
"blob:http://evil.com/abc123",
|
|
"ftp://unsafe-server.com",
|
|
"ssh://attacker.com",
|
|
"smb://network-share",
|
|
"",
|
|
" ",
|
|
"not-a-url",
|
|
];
|
|
|
|
for (const url of SAFE_URLS) {
|
|
it(`should allow safe URL: ${url.substring(0, 40)}`, () => {
|
|
assert.equal(validateBeforeOpen(url), true);
|
|
});
|
|
}
|
|
|
|
for (const url of DANGEROUS_URLS) {
|
|
it(`should block dangerous URL: ${url.substring(0, 40) || "(empty)"}`, () => {
|
|
assert.equal(validateBeforeOpen(url), false);
|
|
});
|
|
}
|
|
});
|