| ... | ... | @@ -112,6 +112,25 @@ function formatErrorMessage(error: unknown): string { |
| 112 | 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 | 134 | function writeDesktopStreamChunk( |
| 116 | 135 | streamName: "stdout" | "stderr", |
| 117 | 136 | chunk: unknown, |
| ... | ... | @@ -1033,23 +1052,13 @@ function registerIpcHandlers(): void { |
| 1033 | 1052 | |
| 1034 | 1053 | ipcMain.removeHandler(OPEN_EXTERNAL_CHANNEL); |
| 1035 | 1054 | ipcMain.handle(OPEN_EXTERNAL_CHANNEL, async (_event, rawUrl: unknown) => { |
| 1036 | | if (typeof rawUrl !== "string" || rawUrl.length === 0) { |
| 1037 | | return false; |
| 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:") { |
| 1055 | const externalUrl = getSafeExternalUrl(rawUrl); |
| 1056 | if (!externalUrl) { |
| 1048 | 1057 | return false; |
| 1049 | 1058 | } |
| 1050 | 1059 | |
| 1051 | 1060 | try { |
| 1052 | | await shell.openExternal(parsedUrl.toString()); |
| 1061 | await shell.openExternal(externalUrl); |
| 1053 | 1062 | return true; |
| 1054 | 1063 | } catch { |
| 1055 | 1064 | return false; |
| ... | ... | @@ -1142,7 +1151,14 @@ function createWindow(): BrowserWindow { |
| 1142 | 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 | 1162 | window.on("page-title-updated", (event) => { |
| 1147 | 1163 | event.preventDefault(); |
| 1148 | 1164 | window.setTitle(APP_DISPLAY_NAME); |