fix(desktop): restrict external links
This commit is contained in:
parent
c7871e14d4
commit
c552cee37e
3 changed files with 31 additions and 1 deletions
19
packages/desktop/src/main/external-link.test.ts
Normal file
19
packages/desktop/src/main/external-link.test.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
import { safeExternalUrl } from "./external-link"
|
||||||
|
|
||||||
|
describe("safeExternalUrl", () => {
|
||||||
|
test("allows web links", () => {
|
||||||
|
expect(safeExternalUrl("https://opencode.ai/docs")).toBe("https://opencode.ai/docs")
|
||||||
|
expect(safeExternalUrl("http://localhost:3000/callback")).toBe("http://localhost:3000/callback")
|
||||||
|
})
|
||||||
|
|
||||||
|
test("rejects executable and application protocols", () => {
|
||||||
|
expect(safeExternalUrl("file:///tmp/script.sh")).toBeUndefined()
|
||||||
|
expect(safeExternalUrl("ssh://attacker.example")).toBeUndefined()
|
||||||
|
expect(safeExternalUrl("custom-handler:payload")).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("rejects malformed URLs", () => {
|
||||||
|
expect(safeExternalUrl("not a url")).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
8
packages/desktop/src/main/external-link.ts
Normal file
8
packages/desktop/src/main/external-link.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
const allowedProtocols = new Set(["https:", "http:"])
|
||||||
|
|
||||||
|
export function safeExternalUrl(value: string) {
|
||||||
|
if (!URL.canParse(value)) return
|
||||||
|
const url = new URL(value)
|
||||||
|
if (!allowedProtocols.has(url.protocol)) return
|
||||||
|
return url.toString()
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import type { DesktopMenuAction } from "@opencode-ai/app/desktop-menu"
|
||||||
import type { FatalRendererError, ServerReadyData, TitlebarTheme } from "../preload/types"
|
import type { FatalRendererError, ServerReadyData, TitlebarTheme } from "../preload/types"
|
||||||
import { runDesktopMenuAction } from "./desktop-menu-actions"
|
import { runDesktopMenuAction } from "./desktop-menu-actions"
|
||||||
import { setForceFocus } from "./debug"
|
import { setForceFocus } from "./debug"
|
||||||
|
import { safeExternalUrl } from "./external-link"
|
||||||
import { assertAttachmentBudget, createPickedFileAuthorizations } from "./attachment-picker"
|
import { assertAttachmentBudget, createPickedFileAuthorizations } from "./attachment-picker"
|
||||||
import { getStore, removeStoreFileIfEmpty } from "./store"
|
import { getStore, removeStoreFileIfEmpty } from "./store"
|
||||||
import { getPinchZoomEnabled, getWindowID, setPinchZoomEnabled, setTitlebar, updateTitlebar } from "./windows"
|
import { getPinchZoomEnabled, getWindowID, setPinchZoomEnabled, setTitlebar, updateTitlebar } from "./windows"
|
||||||
|
|
@ -178,7 +179,9 @@ export function registerIpcHandlers(deps: Deps) {
|
||||||
)
|
)
|
||||||
|
|
||||||
ipcMain.on("open-link", (_event: IpcMainEvent, url: string) => {
|
ipcMain.on("open-link", (_event: IpcMainEvent, url: string) => {
|
||||||
void shell.openExternal(url)
|
const externalUrl = safeExternalUrl(url)
|
||||||
|
if (!externalUrl) return
|
||||||
|
void shell.openExternal(externalUrl)
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle("open-path", async (_event: IpcMainInvokeEvent, path: string, app?: string) => {
|
ipcMain.handle("open-path", async (_event: IpcMainInvokeEvent, path: string, app?: string) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue