| ... | @@ -112,6 +112,25 @@ function formatErrorMessage(error: unknown): string { | ... | @@ -112,6 +112,25 @@ function formatErrorMessage(error: unknown): string { |
| 112 | return String(error); | 112 | return String(error); |
| 113 | } | 113 | } |
| 114 | | 114 | |
| | 115 | function getSafeExternalUrl(rawUrl: unknown): string | null { |
| | 116 | if (typeof rawUrl !== "string" || rawUrl.length === 0) { |
| | 117 | return null; |
| | 118 | } |
| | 119 | |
| | 120 | let parsedUrl: URL; |
| | 121 | try { |
| | 122 | parsedUrl = new URL(rawUrl); |
| | 123 | } catch { |
| | 124 | return null; |
| | 125 | } |
| | 126 | |
| | 127 | if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") { |
| | 128 | return null; |
| | 129 | } |
| | 130 | |
| | 131 | return parsedUrl.toString(); |
| | 132 | } |
| | 133 | |
| 115 | function writeDesktopStreamChunk( | 134 | function writeDesktopStreamChunk( |
| 116 | streamName: "stdout" | "stderr", | 135 | streamName: "stdout" | "stderr", |
| 117 | chunk: unknown, | 136 | chunk: unknown, |
| ... | @@ -1033,23 +1052,13 @@ function registerIpcHandlers(): void { | ... | @@ -1033,23 +1052,13 @@ function registerIpcHandlers(): void { |
| 1033 | | 1052 | |
| 1034 | ipcMain.removeHandler(OPEN_EXTERNAL_CHANNEL); | 1053 | ipcMain.removeHandler(OPEN_EXTERNAL_CHANNEL); |
| 1035 | ipcMain.handle(OPEN_EXTERNAL_CHANNEL, async (_event, rawUrl: unknown) => { | 1054 | ipcMain.handle(OPEN_EXTERNAL_CHANNEL, async (_event, rawUrl: unknown) => { |
| 1036 | if (typeof rawUrl !== "string" || rawUrl.length === 0) { | 1055 | const externalUrl = getSafeExternalUrl(rawUrl); |
| 1037 | return false; | 1056 | if (!externalUrl) { |
| 1038 | } | | |
| 1039 | | | |
| 1040 | let parsedUrl: URL; | | |
| 1041 | try { | | |
| 1042 | parsedUrl = new URL(rawUrl); | | |
| 1043 | } catch { | | |
| 1044 | return false; | | |
| 1045 | } | | |
| 1046 | | | |
| 1047 | if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") { | | |
| 1048 | return false; | 1057 | return false; |
| 1049 | } | 1058 | } |
| 1050 | | 1059 | |
| 1051 | try { | 1060 | try { |
| 1052 | await shell.openExternal(parsedUrl.toString()); | 1061 | await shell.openExternal(externalUrl); |
| 1053 | return true; | 1062 | return true; |
| 1054 | } catch { | 1063 | } catch { |
| 1055 | return false; | 1064 | return false; |
| ... | @@ -1142,7 +1151,14 @@ function createWindow(): BrowserWindow { | ... | @@ -1142,7 +1151,14 @@ function createWindow(): BrowserWindow { |
| 1142 | Menu.buildFromTemplate(menuTemplate).popup({ window }); | 1151 | Menu.buildFromTemplate(menuTemplate).popup({ window }); |
| 1143 | }); | 1152 | }); |
| 1144 | | 1153 | |
| 1145 | window.webContents.setWindowOpenHandler(() => ({ action: "deny" })); | 1154 | window.webContents.setWindowOpenHandler(({ url }) => { |
| | 1155 | const externalUrl = getSafeExternalUrl(url); |
| | 1156 | if (externalUrl) { |
| | 1157 | void shell.openExternal(externalUrl); |
| | 1158 | } |
| | 1159 | return { action: "deny" }; |
| | 1160 | }); |
| | 1161 | |
| 1146 | window.on("page-title-updated", (event) => { | 1162 | window.on("page-title-updated", (event) => { |
| 1147 | event.preventDefault(); | 1163 | event.preventDefault(); |
| 1148 | window.setTitle(APP_DISPLAY_NAME); | 1164 | window.setTitle(APP_DISPLAY_NAME); |