Restrict external browser URLs

This commit is contained in:
ZacharyZhang-NY committed 2026-06-26 19:37:11 -04:00
1 parent d148146b91
commit 91991f7c4a
3 files changed
+25 -7

No files matched your search

@@ -0,0 +1,6 @@
export function normalizeExternalBrowserUrl(value: string): string {
const url = new URL(value);
if (url.protocol !== "https:") throw new Error("External URLs must use HTTPS.");
if (url.username || url.password) throw new Error("External URLs must not include credentials.");
return url.toString();
}
@@ -27,6 +27,7 @@ import { KnowledgeService } from "../services/knowledge-service";
import { AutomationService } from "../services/automation-service";
import { MeetingService } from "../services/meeting-service";
import { SprintService } from "../services/sprint-service";
import { normalizeExternalBrowserUrl } from "./external-url";
export interface IpcDeps {
connection: DatabaseConnection;
@@ -177,8 +178,9 @@ export function registerIpcHandlers(deps: IpcDeps): void {
{ providerKey: input.providerKey, enterpriseDomain: input.enterpriseDomain ?? null },
{
onAuth: ({ url, instructions }) => {
void shell.openExternal(url);
emit({ phase: "authorize", url, instructions });
const browserUrl = normalizeExternalBrowserUrl(url);
void shell.openExternal(browserUrl);
emit({ phase: "authorize", url: browserUrl, instructions });
},
onProgress: (message) => emit({ phase: "progress", message }),
},
@@ -209,11 +211,7 @@ export function registerIpcHandlers(deps: IpcDeps): void {
});
ipcMain.handle(IPC_CHANNELS.openExternalUrl, async (_event, url: string) => {
try {
const parsed = new URL(url);
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
return fail("external_url_blocked", "Only http(s) URLs may be opened externally.");
}
await shell.openExternal(parsed.toString());
await shell.openExternal(normalizeExternalBrowserUrl(url));
return ok(null);
} catch (error) {
return fail("external_url_failed", error instanceof Error ? error.message : String(error));
@@ -0,0 +1,14 @@
import { describe, expect, test } from "vitest";
import { normalizeExternalBrowserUrl } from "../../src/main/app/external-url";
describe("external browser URL validation", () => {
test("normalizes HTTPS URLs and preserves query strings", () => {
expect(normalizeExternalBrowserUrl("https://example.com/docs?q=agent")).toBe("https://example.com/docs?q=agent");
});
test("rejects non-HTTPS and credential-bearing URLs", () => {
expect(() => normalizeExternalBrowserUrl("http://example.com")).toThrow("External URLs must use HTTPS.");
expect(() => normalizeExternalBrowserUrl("mailto:security@example.com")).toThrow("External URLs must use HTTPS.");
expect(() => normalizeExternalBrowserUrl("https://user:token@example.com")).toThrow("External URLs must not include credentials.");
});
});