From b1961ce1cb5fa4da5670fb425b406ea3c0b93a0a Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 2 Jul 2026 15:28:24 +1000 Subject: [PATCH 1/3] Load packaged Desktop from a dedicated goose-app://goose origin --- ui/desktop/src/appProtocol.test.ts | 45 +++++++++ ui/desktop/src/appProtocol.ts | 91 ++++++++++++++++++ ui/desktop/src/gooseServe.test.ts | 143 +++++++++++++++++++---------- ui/desktop/src/gooseServe.ts | 8 +- ui/desktop/src/main.ts | 69 ++++++++++++-- 5 files changed, 294 insertions(+), 62 deletions(-) create mode 100644 ui/desktop/src/appProtocol.test.ts create mode 100644 ui/desktop/src/appProtocol.ts diff --git a/ui/desktop/src/appProtocol.test.ts b/ui/desktop/src/appProtocol.test.ts new file mode 100644 index 000000000000..dc7d146c6f9c --- /dev/null +++ b/ui/desktop/src/appProtocol.test.ts @@ -0,0 +1,45 @@ +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { + PACKAGED_RENDERER_ORIGIN, + packagedRendererUrl, + rendererContentType, + resolvePackagedRendererPath, +} from './appProtocol'; + +describe('appProtocol', () => { + it('uses the packaged renderer app origin', () => { + expect(PACKAGED_RENDERER_ORIGIN).toBe('goose-app://goose'); + expect(packagedRendererUrl().toString()).toBe('goose-app://goose/index.html'); + }); + + it('resolves packaged renderer asset paths under the renderer root', () => { + const root = path.resolve('/tmp/goose-renderer'); + + expect(resolvePackagedRendererPath('goose-app://goose/', root)).toBe( + path.join(root, 'index.html') + ); + expect(resolvePackagedRendererPath('goose-app://goose/assets/index.js', root)).toBe( + path.join(root, 'assets', 'index.js') + ); + }); + + it('rejects non-renderer URLs and path traversal', () => { + const root = path.resolve('/tmp/goose-renderer'); + + expect(resolvePackagedRendererPath('https://goose/index.html', root)).toBeNull(); + expect(resolvePackagedRendererPath('goose-app://other/index.html', root)).toBeNull(); + expect(resolvePackagedRendererPath('goose-app://goose/%2e%2e/settings.json', root)).toBeNull(); + expect( + resolvePackagedRendererPath('goose-app://goose/assets%5C..%5Csettings.json', root) + ).toBeNull(); + }); + + it('returns content types for renderer assets', () => { + expect(rendererContentType('/tmp/index.html')).toBe('text/html; charset=utf-8'); + expect(rendererContentType('/tmp/assets/index.js')).toBe('text/javascript; charset=utf-8'); + expect(rendererContentType('/tmp/assets/index.css')).toBe('text/css; charset=utf-8'); + expect(rendererContentType('/tmp/assets/font.woff2')).toBe('font/woff2'); + expect(rendererContentType('/tmp/assets/file.bin')).toBe('application/octet-stream'); + }); +}); diff --git a/ui/desktop/src/appProtocol.ts b/ui/desktop/src/appProtocol.ts new file mode 100644 index 000000000000..0992ad1ac229 --- /dev/null +++ b/ui/desktop/src/appProtocol.ts @@ -0,0 +1,91 @@ +import path from 'node:path'; + +export const PACKAGED_RENDERER_PROTOCOL = 'goose-app'; +export const PACKAGED_RENDERER_HOST = 'goose'; +export const PACKAGED_RENDERER_ORIGIN = `${PACKAGED_RENDERER_PROTOCOL}://${PACKAGED_RENDERER_HOST}`; + +export function packagedRendererUrl(): URL { + return new URL(`${PACKAGED_RENDERER_ORIGIN}/index.html`); +} + +function containsTraversalSegment(requestUrl: string): boolean { + return /(?:^|\/|%2f|\\|%5c)(?:\.\.|%2e%2e)(?:$|\/|%2f|\\|%5c|\?|#)/i.test(requestUrl); +} + +export function resolvePackagedRendererPath( + requestUrl: string, + rendererRoot: string +): string | null { + if (containsTraversalSegment(requestUrl)) { + return null; + } + + let url: URL; + try { + url = new URL(requestUrl); + } catch { + return null; + } + + if ( + url.protocol !== `${PACKAGED_RENDERER_PROTOCOL}:` || + url.hostname !== PACKAGED_RENDERER_HOST + ) { + return null; + } + + let pathname: string; + try { + pathname = decodeURIComponent(url.pathname); + } catch { + return null; + } + + const relativePath = pathname === '/' ? 'index.html' : pathname.replace(/^\/+/, ''); + if (!relativePath || relativePath.includes('\0') || relativePath.includes('\\')) { + return null; + } + + const root = path.resolve(rendererRoot); + const resolvedPath = path.resolve(root, relativePath); + if (resolvedPath !== root && !resolvedPath.startsWith(`${root}${path.sep}`)) { + return null; + } + + return resolvedPath; +} + +export function rendererContentType(filePath: string): string { + switch (path.extname(filePath).toLowerCase()) { + case '.html': + return 'text/html; charset=utf-8'; + case '.js': + case '.mjs': + return 'text/javascript; charset=utf-8'; + case '.css': + return 'text/css; charset=utf-8'; + case '.json': + return 'application/json; charset=utf-8'; + case '.svg': + return 'image/svg+xml'; + case '.png': + return 'image/png'; + case '.jpg': + case '.jpeg': + return 'image/jpeg'; + case '.gif': + return 'image/gif'; + case '.webp': + return 'image/webp'; + case '.ico': + return 'image/x-icon'; + case '.wasm': + return 'application/wasm'; + case '.woff': + return 'font/woff'; + case '.woff2': + return 'font/woff2'; + default: + return 'application/octet-stream'; + } +} diff --git a/ui/desktop/src/gooseServe.test.ts b/ui/desktop/src/gooseServe.test.ts index 2ee41f2eadaa..08cb056a1fce 100644 --- a/ui/desktop/src/gooseServe.test.ts +++ b/ui/desktop/src/gooseServe.test.ts @@ -2,6 +2,7 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; +import { PACKAGED_RENDERER_ORIGIN } from './appProtocol'; import { buildLocalServeUrls, findGooseBinaryPath, startGooseServe } from './gooseServe'; const binaryName = process.platform === 'win32' ? 'goose.exe' : 'goose'; @@ -202,80 +203,124 @@ describe('startGooseServe', () => { } }); - it.skipIf(process.platform === 'win32')('uses TLS URLs and args when TLS is enabled', async () => { + it.skipIf(process.platform === 'win32')( + 'uses TLS URLs and args when TLS is enabled', + async () => { + const tempDir = makeTempDir(); + const argsPath = path.join(tempDir, 'args.txt'); + const goosePath = makeExecutable( + path.join(tempDir, 'goose'), + [ + '#!/usr/bin/env sh', + 'printf "%s\\n" "$@" > "$TEST_ARGS_PATH"', + 'printf "GOOSED_CERT_FINGERPRINT=DD:EE:FF\\n"', + 'while true; do sleep 1; done', + '', + ].join('\n') + ); + vi.stubEnv('GOOSE_BINARY', goosePath); + + const readinessUrls: string[] = []; + const logger = { + info: vi.fn(), + error: vi.fn(), + }; + const readinessFetch = vi.fn(async (input: string, _init?: ReadinessFetchInit) => { + readinessUrls.push(input); + return new Response(null, { status: 200 }); + }); + + const result = await startGooseServe({ + serverSecret: 'test-secret', + dir: tempDir, + tls: true, + env: { + TEST_ARGS_PATH: argsPath, + }, + logger, + readinessFetch, + }); + + try { + expect(readinessUrls[0]).toMatch(/^https:\/\/127\.0\.0\.1:\d+\/status$/); + expect(result.acpUrl).toMatch(/^wss:\/\/127\.0\.0\.1:\d+\/acp\?token=test-secret$/); + expect(result.certFingerprint).toBe('DD:EE:FF'); + const args = await waitForFileLines(argsPath); + expect(args).toContain('--tls'); + expect(args).not.toContain('--allowed-origin'); + } finally { + await result.cleanup(); + } + } + ); + + it.skipIf(process.platform === 'win32')('passes allowed origins to goose serve', async () => { const tempDir = makeTempDir(); + const resourcesPath = path.join(tempDir, 'resources'); const argsPath = path.join(tempDir, 'args.txt'); - const goosePath = makeExecutable( - path.join(tempDir, 'goose'), + makeExecutable( + path.join(resourcesPath, 'bin', binaryName), [ '#!/usr/bin/env sh', 'printf "%s\\n" "$@" > "$TEST_ARGS_PATH"', - 'printf "GOOSED_CERT_FINGERPRINT=DD:EE:FF\\n"', 'while true; do sleep 1; done', '', ].join('\n') ); - vi.stubEnv('GOOSE_BINARY', goosePath); - const readinessUrls: string[] = []; - const logger = { - info: vi.fn(), - error: vi.fn(), - }; - const readinessFetch = vi.fn(async (input: string, _init?: ReadinessFetchInit) => { - readinessUrls.push(input); - return new Response(null, { status: 200 }); - }); + const readinessFetch = vi.fn(async () => new Response(null, { status: 200 })); const result = await startGooseServe({ serverSecret: 'test-secret', dir: tempDir, - tls: true, + isPackaged: true, + resourcesPath, + allowedOrigins: [PACKAGED_RENDERER_ORIGIN], env: { TEST_ARGS_PATH: argsPath, }, - logger, readinessFetch, }); try { - expect(readinessUrls[0]).toMatch(/^https:\/\/127\.0\.0\.1:\d+\/status$/); - expect(result.acpUrl).toMatch(/^wss:\/\/127\.0\.0\.1:\d+\/acp\?token=test-secret$/); - expect(result.certFingerprint).toBe('DD:EE:FF'); - await expect(waitForFileLines(argsPath)).resolves.toContain('--tls'); + const args = await waitForFileLines(argsPath); + expect(args).toEqual(expect.arrayContaining(['--allowed-origin', PACKAGED_RENDERER_ORIGIN])); } finally { await result.cleanup(); } }); - it.skipIf(process.platform === 'win32')('waits for TLS fingerprint after readiness succeeds', async () => { - const tempDir = makeTempDir(); - const goosePath = makeExecutable( - path.join(tempDir, 'goose'), - [ - '#!/usr/bin/env sh', - 'sleep 0.1', - 'printf "GOOSED_CERT_FINGERPRINT=11:22:33\\n"', - 'while true; do sleep 1; done', - '', - ].join('\n') - ); - vi.stubEnv('GOOSE_BINARY', goosePath); - - const readinessFetch = vi.fn(async () => new Response(null, { status: 200 })); - - const result = await startGooseServe({ - serverSecret: 'test-secret', - dir: tempDir, - tls: true, - readinessFetch, - }); - - try { - expect(readinessFetch).toHaveBeenCalled(); - expect(result.certFingerprint).toBe('11:22:33'); - } finally { - await result.cleanup(); + it.skipIf(process.platform === 'win32')( + 'waits for TLS fingerprint after readiness succeeds', + async () => { + const tempDir = makeTempDir(); + const goosePath = makeExecutable( + path.join(tempDir, 'goose'), + [ + '#!/usr/bin/env sh', + 'sleep 0.1', + 'printf "GOOSED_CERT_FINGERPRINT=11:22:33\\n"', + 'while true; do sleep 1; done', + '', + ].join('\n') + ); + vi.stubEnv('GOOSE_BINARY', goosePath); + + const readinessFetch = vi.fn(async () => new Response(null, { status: 200 })); + + const result = await startGooseServe({ + serverSecret: 'test-secret', + dir: tempDir, + tls: true, + readinessFetch, + }); + + try { + expect(readinessFetch).toHaveBeenCalled(); + expect(result.certFingerprint).toBe('11:22:33'); + } finally { + await result.cleanup(); + } } - }); + ); }); diff --git a/ui/desktop/src/gooseServe.ts b/ui/desktop/src/gooseServe.ts index bfaccaed18a4..51f663b7f574 100644 --- a/ui/desktop/src/gooseServe.ts +++ b/ui/desktop/src/gooseServe.ts @@ -32,6 +32,7 @@ export interface StartGooseServeOptions extends FindGooseBinaryOptions { dir?: string; serverSecret: string; tls?: boolean; + allowedOrigins?: string[]; env?: Record; logger?: Logger; diagnosticsDir?: string; @@ -135,10 +136,7 @@ const appendErrorTail = (target: string[], lines: string[], maxLines = 100): voi const CERT_FINGERPRINT_PREFIX = 'GOOSED_CERT_FINGERPRINT='; const TLS_FINGERPRINT_TIMEOUT_MS = 5000; -const fetchStatus = async ( - statusUrl: string, - readinessFetch: ReadinessFetch -): Promise => { +const fetchStatus = async (statusUrl: string, readinessFetch: ReadinessFetch): Promise => { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 1000); @@ -321,6 +319,7 @@ export const startGooseServe = async ({ dir, serverSecret, tls = false, + allowedOrigins = [], env: additionalEnv = {}, isPackaged, resourcesPath, @@ -358,6 +357,7 @@ export const startGooseServe = async ({ const args = [ 'serve', ...(tls ? ['--tls'] : []), + ...allowedOrigins.flatMap((origin) => ['--allowed-origin', origin]), '--platform', 'desktop', '--host', diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index dd938be40c17..94c68ecd5226 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -11,12 +11,13 @@ import { net, Notification, powerSaveBlocker, + protocol, screen, session, shell, Tray, } from 'electron'; -import { pathToFileURL, format as formatUrl, URLSearchParams } from 'node:url'; +import { format as formatUrl, URLSearchParams } from 'node:url'; import { Buffer } from 'node:buffer'; import fs from 'node:fs/promises'; import fsSync from 'node:fs'; @@ -54,6 +55,25 @@ import type { GooseApp } from './types/apps'; import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer'; import { BLOCKED_PROTOCOLS, WEB_PROTOCOLS } from './utils/urlSecurity'; import { buildCSP } from './utils/csp'; +import { + PACKAGED_RENDERER_ORIGIN, + PACKAGED_RENDERER_PROTOCOL, + packagedRendererUrl, + rendererContentType, + resolvePackagedRendererPath, +} from './appProtocol'; + +protocol.registerSchemesAsPrivileged([ + { + scheme: PACKAGED_RENDERER_PROTOCOL, + privileges: { + standard: true, + secure: true, + supportFetchAPI: true, + corsEnabled: true, + }, + }, +]); function shouldSetupUpdater(): boolean { // Setup updater if either the flag is enabled OR dev updates are enabled @@ -817,10 +837,44 @@ async function handleFileOpen(filePath: string) { declare var MAIN_WINDOW_VITE_DEV_SERVER_URL: string; declare var MAIN_WINDOW_VITE_NAME: string; +function getPackagedRendererRoot(): string { + return path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}`); +} + +let packagedRendererProtocolRegistered = false; + +function registerPackagedRendererProtocol() { + if (MAIN_WINDOW_VITE_DEV_SERVER_URL || packagedRendererProtocolRegistered) { + return; + } + + protocol.handle(PACKAGED_RENDERER_PROTOCOL, async (request) => { + const filePath = resolvePackagedRendererPath(request.url, getPackagedRendererRoot()); + if (!filePath) { + return new Response('Not found', { status: 404 }); + } + + try { + const data = await fs.readFile(filePath); + return new Response(new Uint8Array(data), { + headers: { + 'Content-Type': rendererContentType(filePath), + }, + }); + } catch { + return new Response('Not found', { status: 404 }); + } + }); + packagedRendererProtocolRegistered = true; +} + function getAppUrl(): URL { - return MAIN_WINDOW_VITE_DEV_SERVER_URL - ? new URL(MAIN_WINDOW_VITE_DEV_SERVER_URL) - : pathToFileURL(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)); + if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { + return new URL(MAIN_WINDOW_VITE_DEV_SERVER_URL); + } + + registerPackagedRendererProtocol(); + return packagedRendererUrl(); } // Parse command line arguments @@ -1147,6 +1201,7 @@ const createChat = async ( serverSecret, dir: workingDir, tls: true, + allowedOrigins: app.isPackaged ? [PACKAGED_RENDERER_ORIGIN] : undefined, env: { GOOSE_PATH_ROOT: appConfig.GOOSE_PATH_ROOT as string | undefined, }, @@ -2409,6 +2464,7 @@ const registerGlobalShortcuts = () => { async function appMain() { await configureProxy(); + registerPackagedRendererProtocol(); // Ensure Windows shims are available before any MCP processes are spawned await ensureWinShims(); @@ -2451,11 +2507,6 @@ async function appMain() { // Register global shortcuts based on settings registerGlobalShortcuts(); - session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => { - details.requestHeaders['Origin'] = 'http://localhost:5173'; - callback({ cancel: false, requestHeaders: details.requestHeaders }); - }); - if (settings.showMenuBarIcon) { createTray(); } From 6e6710ced345a7e273ef8ae82e2fb53b64e2cfa2 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 2 Jul 2026 16:07:52 +1000 Subject: [PATCH 2/3] fixed the rendering --- ui/desktop/src/appProtocol.test.ts | 2 ++ ui/desktop/src/appProtocol.ts | 1 + ui/desktop/src/main.ts | 41 ++++++++++++++++-------------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/ui/desktop/src/appProtocol.test.ts b/ui/desktop/src/appProtocol.test.ts index dc7d146c6f9c..9387370dbd11 100644 --- a/ui/desktop/src/appProtocol.test.ts +++ b/ui/desktop/src/appProtocol.test.ts @@ -1,6 +1,7 @@ import path from 'node:path'; import { describe, expect, it } from 'vitest'; import { + GOOSE_SESSION_PARTITION, PACKAGED_RENDERER_ORIGIN, packagedRendererUrl, rendererContentType, @@ -10,6 +11,7 @@ import { describe('appProtocol', () => { it('uses the packaged renderer app origin', () => { expect(PACKAGED_RENDERER_ORIGIN).toBe('goose-app://goose'); + expect(GOOSE_SESSION_PARTITION).toBe('persist:goose'); expect(packagedRendererUrl().toString()).toBe('goose-app://goose/index.html'); }); diff --git a/ui/desktop/src/appProtocol.ts b/ui/desktop/src/appProtocol.ts index 0992ad1ac229..f664a67e497f 100644 --- a/ui/desktop/src/appProtocol.ts +++ b/ui/desktop/src/appProtocol.ts @@ -3,6 +3,7 @@ import path from 'node:path'; export const PACKAGED_RENDERER_PROTOCOL = 'goose-app'; export const PACKAGED_RENDERER_HOST = 'goose'; export const PACKAGED_RENDERER_ORIGIN = `${PACKAGED_RENDERER_PROTOCOL}://${PACKAGED_RENDERER_HOST}`; +export const GOOSE_SESSION_PARTITION = 'persist:goose'; export function packagedRendererUrl(): URL { return new URL(`${PACKAGED_RENDERER_ORIGIN}/index.html`); diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 94c68ecd5226..17e4f9d0435a 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -56,6 +56,7 @@ import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-insta import { BLOCKED_PROTOCOLS, WEB_PROTOCOLS } from './utils/urlSecurity'; import { buildCSP } from './utils/csp'; import { + GOOSE_SESSION_PARTITION, PACKAGED_RENDERER_ORIGIN, PACKAGED_RENDERER_PROTOCOL, packagedRendererUrl, @@ -848,23 +849,25 @@ function registerPackagedRendererProtocol() { return; } - protocol.handle(PACKAGED_RENDERER_PROTOCOL, async (request) => { - const filePath = resolvePackagedRendererPath(request.url, getPackagedRendererRoot()); - if (!filePath) { - return new Response('Not found', { status: 404 }); - } + session + .fromPartition(GOOSE_SESSION_PARTITION) + .protocol.handle(PACKAGED_RENDERER_PROTOCOL, async (request) => { + const filePath = resolvePackagedRendererPath(request.url, getPackagedRendererRoot()); + if (!filePath) { + return new Response('Not found', { status: 404 }); + } - try { - const data = await fs.readFile(filePath); - return new Response(new Uint8Array(data), { - headers: { - 'Content-Type': rendererContentType(filePath), - }, - }); - } catch { - return new Response('Not found', { status: 404 }); - } - }); + try { + const data = await fs.readFile(filePath); + return new Response(new Uint8Array(data), { + headers: { + 'Content-Type': rendererContentType(filePath), + }, + }); + } catch { + return new Response('Not found', { status: 404 }); + } + }); packagedRendererProtocolRegistered = true; } @@ -1316,7 +1319,7 @@ const createChat = async ( process.env.SECURITY_COMMAND_CLASSIFIER_ENABLED_OVERRIDE, }), ], - partition: 'persist:goose', + partition: GOOSE_SESSION_PARTITION, }, }); } catch (error) { @@ -1582,7 +1585,7 @@ const createLauncher = () => { GOOSE_LOCALE: getConfiguredGooseLocale(), }), ], - partition: 'persist:goose', + partition: GOOSE_SESSION_PARTITION, }, skipTaskbar: true, alwaysOnTop: true, @@ -3048,7 +3051,7 @@ async function appMain() { GOOSE_VERSION: version, }), ], - partition: 'persist:goose', + partition: GOOSE_SESSION_PARTITION, }, }); From 82534c650f884b2790c9b84ae126b04572b61280 Mon Sep 17 00:00:00 2001 From: Lifei Zhou Date: Thu, 2 Jul 2026 16:24:47 +1000 Subject: [PATCH 3/3] added info for external server users --- .../settings/app/ExternalBackendSection.tsx | 33 ++++++++++++------- ui/desktop/src/i18n/messages/de.json | 3 ++ ui/desktop/src/i18n/messages/en.json | 3 ++ ui/desktop/src/i18n/messages/es.json | 3 ++ ui/desktop/src/i18n/messages/fr.json | 3 ++ ui/desktop/src/i18n/messages/hi.json | 3 ++ ui/desktop/src/i18n/messages/id.json | 3 ++ ui/desktop/src/i18n/messages/it.json | 3 ++ ui/desktop/src/i18n/messages/ja.json | 3 ++ ui/desktop/src/i18n/messages/ko.json | 3 ++ ui/desktop/src/i18n/messages/ms.json | 3 ++ ui/desktop/src/i18n/messages/pt.json | 3 ++ ui/desktop/src/i18n/messages/ru.json | 3 ++ ui/desktop/src/i18n/messages/tr.json | 3 ++ ui/desktop/src/i18n/messages/vi.json | 3 ++ ui/desktop/src/i18n/messages/zh-CN.json | 3 ++ ui/desktop/src/i18n/messages/zh-TW.json | 3 ++ 17 files changed, 69 insertions(+), 12 deletions(-) diff --git a/ui/desktop/src/components/settings/app/ExternalBackendSection.tsx b/ui/desktop/src/components/settings/app/ExternalBackendSection.tsx index 408a1a35ac39..ab27f67beb1a 100644 --- a/ui/desktop/src/components/settings/app/ExternalBackendSection.tsx +++ b/ui/desktop/src/components/settings/app/ExternalBackendSection.tsx @@ -6,6 +6,7 @@ import { AlertCircle } from 'lucide-react'; import { ExternalGoosedConfig, defaultSettings } from '../../../utils/settings'; import { defineMessages, useIntl } from '../../../i18n'; import { normalizeAcpHttpBaseUrl } from '../../../acp/url'; +import { PACKAGED_RENDERER_ORIGIN } from '../../../appProtocol'; const i18n = defineMessages({ title: { @@ -34,6 +35,11 @@ const i18n = defineMessages({ defaultMessage: 'Enter the HTTP(S) base URL. Goose checks /status and connects to /acp under this base.', }, + allowedOriginHelp: { + id: 'externalBackendSection.allowedOriginHelp', + defaultMessage: + 'Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}.', + }, secretKey: { id: 'externalBackendSection.secretKey', defaultMessage: 'Secret Key', @@ -56,12 +62,12 @@ const i18n = defineMessages({ }, certFingerprintHelp: { id: 'externalBackendSection.certFingerprintHelp', - defaultMessage: 'Pin a specific TLS certificate fingerprint. If omitted, the certificate is trusted on first use (TOFU).', + defaultMessage: + 'Pin a specific TLS certificate fingerprint. If omitted, the certificate is trusted on first use (TOFU).', }, restartNote: { id: 'externalBackendSection.restartNote', - defaultMessage: - 'Changes apply to new chat windows. Restart Goose to update existing windows.', + defaultMessage: 'Changes apply to new chat windows. Restart Goose to update existing windows.', }, urlProtocolError: { id: 'externalBackendSection.urlProtocolError', @@ -77,7 +83,8 @@ const i18n = defineMessages({ }, urlBaseError: { id: 'externalBackendSection.urlBaseError', - defaultMessage: 'URL must be the backend base URL before /acp, without query parameters or fragments', + defaultMessage: + 'URL must be the backend base URL before /acp, without query parameters or fragments', }, }); @@ -95,10 +102,7 @@ export default function ExternalBackendSection() { loadSettings(); }, []); - const validateUrl = ( - value: string, - certFingerprint = config.certFingerprint - ): boolean => { + const validateUrl = (value: string, certFingerprint = config.certFingerprint): boolean => { if (!value) { setUrlError(null); return true; @@ -175,14 +179,14 @@ export default function ExternalBackendSection() { {intl.formatMessage(i18n.title)} - - {intl.formatMessage(i18n.description)} - + {intl.formatMessage(i18n.description)}
-

{intl.formatMessage(i18n.useExternalServer)}

+

+ {intl.formatMessage(i18n.useExternalServer)} +

{intl.formatMessage(i18n.useExternalServerDescription)}

@@ -222,6 +226,11 @@ export default function ExternalBackendSection() {

{intl.formatMessage(i18n.serverUrlHelp)}

+

+ {intl.formatMessage(i18n.allowedOriginHelp, { + origin: PACKAGED_RENDERER_ORIGIN, + })} +

diff --git a/ui/desktop/src/i18n/messages/de.json b/ui/desktop/src/i18n/messages/de.json index 86284cbaf5c7..8ed362134350 100644 --- a/ui/desktop/src/i18n/messages/de.json +++ b/ui/desktop/src/i18n/messages/de.json @@ -1163,6 +1163,9 @@ "extensionsView.searchPlaceholder": { "defaultMessage": "Erweiterungen suchen..." }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Zertifikat-Fingerabdruck (optional)" }, diff --git a/ui/desktop/src/i18n/messages/en.json b/ui/desktop/src/i18n/messages/en.json index 1218b3c90a5c..3999de181b65 100644 --- a/ui/desktop/src/i18n/messages/en.json +++ b/ui/desktop/src/i18n/messages/en.json @@ -1163,6 +1163,9 @@ "extensionsView.searchPlaceholder": { "defaultMessage": "Search extensions..." }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Certificate Fingerprint (optional)" }, diff --git a/ui/desktop/src/i18n/messages/es.json b/ui/desktop/src/i18n/messages/es.json index 2f79a6ef0cc5..b9f38899519e 100644 --- a/ui/desktop/src/i18n/messages/es.json +++ b/ui/desktop/src/i18n/messages/es.json @@ -1163,6 +1163,9 @@ "extensionTimeoutField.timeoutLabel": { "defaultMessage": "Tiempo de espera" }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Huella del certificado (opcional)" }, diff --git a/ui/desktop/src/i18n/messages/fr.json b/ui/desktop/src/i18n/messages/fr.json index af43762c044f..c95772904bd4 100644 --- a/ui/desktop/src/i18n/messages/fr.json +++ b/ui/desktop/src/i18n/messages/fr.json @@ -1163,6 +1163,9 @@ "extensionsView.searchPlaceholder": { "defaultMessage": "Rechercher des extensions..." }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Empreinte du certificat (facultatif)" }, diff --git a/ui/desktop/src/i18n/messages/hi.json b/ui/desktop/src/i18n/messages/hi.json index 97d03c2095c8..9e5201070d8f 100644 --- a/ui/desktop/src/i18n/messages/hi.json +++ b/ui/desktop/src/i18n/messages/hi.json @@ -1163,6 +1163,9 @@ "extensionTimeoutField.timeoutLabel": { "defaultMessage": "समयबाह्य" }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "प्रमाणपत्र फ़िंगरप्रिंट (वैकल्पिक)" }, diff --git a/ui/desktop/src/i18n/messages/id.json b/ui/desktop/src/i18n/messages/id.json index 4c32691dac23..4db50c4d417d 100644 --- a/ui/desktop/src/i18n/messages/id.json +++ b/ui/desktop/src/i18n/messages/id.json @@ -1163,6 +1163,9 @@ "extensionsView.searchPlaceholder": { "defaultMessage": "Cari ekstensi..." }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Sidik Jari Sertifikat (opsional)" }, diff --git a/ui/desktop/src/i18n/messages/it.json b/ui/desktop/src/i18n/messages/it.json index 6cc0c568e5e0..ae862624b64d 100644 --- a/ui/desktop/src/i18n/messages/it.json +++ b/ui/desktop/src/i18n/messages/it.json @@ -1163,6 +1163,9 @@ "extensionsView.searchPlaceholder": { "defaultMessage": "Cerca estensioni..." }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Impronta del certificato (facoltativa)" }, diff --git a/ui/desktop/src/i18n/messages/ja.json b/ui/desktop/src/i18n/messages/ja.json index 502fe5ff1ec5..6e03d27bdd51 100644 --- a/ui/desktop/src/i18n/messages/ja.json +++ b/ui/desktop/src/i18n/messages/ja.json @@ -1163,6 +1163,9 @@ "extensionTimeoutField.timeoutLabel": { "defaultMessage": "タイムアウト" }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "証明書フィンガープリント(任意)" }, diff --git a/ui/desktop/src/i18n/messages/ko.json b/ui/desktop/src/i18n/messages/ko.json index 3916658c8ef4..aed6c4f2d779 100644 --- a/ui/desktop/src/i18n/messages/ko.json +++ b/ui/desktop/src/i18n/messages/ko.json @@ -1163,6 +1163,9 @@ "extensionTimeoutField.timeoutLabel": { "defaultMessage": "시간 초과" }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "인증서 지문(선택 사항)" }, diff --git a/ui/desktop/src/i18n/messages/ms.json b/ui/desktop/src/i18n/messages/ms.json index 205a40b9a957..4ddf8f45cdbf 100644 --- a/ui/desktop/src/i18n/messages/ms.json +++ b/ui/desktop/src/i18n/messages/ms.json @@ -1163,6 +1163,9 @@ "extensionsView.searchPlaceholder": { "defaultMessage": "Cari sambungan..." }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Cap Jari Sijil (pilihan)" }, diff --git a/ui/desktop/src/i18n/messages/pt.json b/ui/desktop/src/i18n/messages/pt.json index 9e4edd8b28ec..03b03845fcc1 100644 --- a/ui/desktop/src/i18n/messages/pt.json +++ b/ui/desktop/src/i18n/messages/pt.json @@ -1163,6 +1163,9 @@ "extensionsView.searchPlaceholder": { "defaultMessage": "Pesquisar extensões..." }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Impressão digital do certificado (opcional)" }, diff --git a/ui/desktop/src/i18n/messages/ru.json b/ui/desktop/src/i18n/messages/ru.json index 406f75efe316..28e931d12ef5 100644 --- a/ui/desktop/src/i18n/messages/ru.json +++ b/ui/desktop/src/i18n/messages/ru.json @@ -1163,6 +1163,9 @@ "extensionTimeoutField.timeoutLabel": { "defaultMessage": "Тайм-аут" }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Отпечаток сертификата (необязательно)" }, diff --git a/ui/desktop/src/i18n/messages/tr.json b/ui/desktop/src/i18n/messages/tr.json index 93c079a2bc13..78294e47802e 100644 --- a/ui/desktop/src/i18n/messages/tr.json +++ b/ui/desktop/src/i18n/messages/tr.json @@ -1163,6 +1163,9 @@ "extensionTimeoutField.timeoutLabel": { "defaultMessage": "Zaman aşımı" }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Sertifika Parmak İzi (isteğe bağlı)" }, diff --git a/ui/desktop/src/i18n/messages/vi.json b/ui/desktop/src/i18n/messages/vi.json index 9fbce143e852..a57dde314b97 100644 --- a/ui/desktop/src/i18n/messages/vi.json +++ b/ui/desktop/src/i18n/messages/vi.json @@ -1163,6 +1163,9 @@ "extensionsView.searchPlaceholder": { "defaultMessage": "Tìm kiếm tiện ích mở rộng..." }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "Dấu vân tay chứng chỉ (tùy chọn)" }, diff --git a/ui/desktop/src/i18n/messages/zh-CN.json b/ui/desktop/src/i18n/messages/zh-CN.json index fb1a51620c37..8a072424ae04 100644 --- a/ui/desktop/src/i18n/messages/zh-CN.json +++ b/ui/desktop/src/i18n/messages/zh-CN.json @@ -1163,6 +1163,9 @@ "extensionTimeoutField.timeoutLabel": { "defaultMessage": "超时" }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "证书指纹(可选)" }, diff --git a/ui/desktop/src/i18n/messages/zh-TW.json b/ui/desktop/src/i18n/messages/zh-TW.json index 47b8f7b2d42e..b96f201e05c5 100644 --- a/ui/desktop/src/i18n/messages/zh-TW.json +++ b/ui/desktop/src/i18n/messages/zh-TW.json @@ -1163,6 +1163,9 @@ "extensionsView.searchPlaceholder": { "defaultMessage": "搜尋擴充功能…" }, + "externalBackendSection.allowedOriginHelp": { + "defaultMessage": "Packaged Goose Desktop connects from origin {origin}. Start external goose serve with --allowed-origin {origin}." + }, "externalBackendSection.certFingerprint": { "defaultMessage": "憑證指紋(選填)" },