Restrict external browser URLs
This commit is contained in:
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 { AutomationService } from "../services/automation-service";
|
||||||
import { MeetingService } from "../services/meeting-service";
|
import { MeetingService } from "../services/meeting-service";
|
||||||
import { SprintService } from "../services/sprint-service";
|
import { SprintService } from "../services/sprint-service";
|
||||||
|
import { normalizeExternalBrowserUrl } from "./external-url";
|
||||||
|
|
||||||
export interface IpcDeps {
|
export interface IpcDeps {
|
||||||
connection: DatabaseConnection;
|
connection: DatabaseConnection;
|
||||||
@@ -177,8 +178,9 @@ export function registerIpcHandlers(deps: IpcDeps): void {
|
|||||||
{ providerKey: input.providerKey, enterpriseDomain: input.enterpriseDomain ?? null },
|
{ providerKey: input.providerKey, enterpriseDomain: input.enterpriseDomain ?? null },
|
||||||
{
|
{
|
||||||
onAuth: ({ url, instructions }) => {
|
onAuth: ({ url, instructions }) => {
|
||||||
void shell.openExternal(url);
|
const browserUrl = normalizeExternalBrowserUrl(url);
|
||||||
emit({ phase: "authorize", url, instructions });
|
void shell.openExternal(browserUrl);
|
||||||
|
emit({ phase: "authorize", url: browserUrl, instructions });
|
||||||
},
|
},
|
||||||
onProgress: (message) => emit({ phase: "progress", message }),
|
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) => {
|
ipcMain.handle(IPC_CHANNELS.openExternalUrl, async (_event, url: string) => {
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(url);
|
await shell.openExternal(normalizeExternalBrowserUrl(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());
|
|
||||||
return ok(null);
|
return ok(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return fail("external_url_failed", error instanceof Error ? error.message : String(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.");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user