diff --git a/site/src/modules/apps/apps.test.ts b/site/src/modules/apps/apps.test.ts index 28d52fb9ffc57..71d133c23920f 100644 --- a/site/src/modules/apps/apps.test.ts +++ b/site/src/modules/apps/apps.test.ts @@ -117,6 +117,36 @@ describe("getAppHref", () => { expect(href).toBe(externalApp.url); }); + it("returns the raw URL when external app URL is invalid", () => { + const externalApp = { + ...MockWorkspaceApp, + external: true, + url: "my-app", + }; + const href = getAppHref(externalApp, { + host: "*.apps-host.tld", + path: "/path-base", + agent: MockWorkspaceAgent, + workspace: MockWorkspace, + }); + expect(href).toBe("my-app"); + }); + + it("returns the raw URL when external app URL is an empty string", () => { + const externalApp = { + ...MockWorkspaceApp, + external: true, + url: "", + }; + const href = getAppHref(externalApp, { + host: "*.apps-host.tld", + path: "/path-base", + agent: MockWorkspaceAgent, + workspace: MockWorkspace, + }); + expect(href).toBe(""); + }); + it("returns a path when app doesn't use a subdomain", () => { const app = { ...MockWorkspaceApp, diff --git a/site/src/modules/apps/apps.ts b/site/src/modules/apps/apps.ts index 3bd96c227a47c..c806e11617484 100644 --- a/site/src/modules/apps/apps.ts +++ b/site/src/modules/apps/apps.ts @@ -117,7 +117,15 @@ export const getAppHref = ( { path, token, workspace, agent, host }: GetAppHrefParams, ): string => { if (isExternalApp(app)) { - const appProtocol = new URL(app.url).protocol; + let appProtocol: string; + try { + appProtocol = new URL(app.url).protocol; + } catch { + // If the URL is invalid (e.g. a bare string with no + // scheme), return it as-is so one misconfigured app + // never crashes the entire dashboard. + return app.url; + } const isAllowedProtocol = ALLOWED_EXTERNAL_APP_PROTOCOLS.includes(appProtocol);