diff --git a/ui/src/pages/ExecutionWorkspaceDetail.test.tsx b/ui/src/pages/ExecutionWorkspaceDetail.test.tsx index 22e68eb79c3..251dbbfe054 100644 --- a/ui/src/pages/ExecutionWorkspaceDetail.test.tsx +++ b/ui/src/pages/ExecutionWorkspaceDetail.test.tsx @@ -437,6 +437,28 @@ describe("ExecutionWorkspaceDetail plugin slots", () => { expect(container.querySelector('[data-testid="plugin-slot-mount"]')).toBeNull(); }); + it("does not render protocol-relative metadata work-product links", async () => { + mockRouteLocation.pathname = "/execution-workspaces/workspace-1/work-products"; + mockIssuesApi.list.mockResolvedValue([issue()]); + mockExecutionWorkspacesApi.get.mockResolvedValue(workspace({ + workProducts: [ + workProduct({ + id: "wp-file", + title: "agent-report.html", + metadata: { + openPath: "//evil.example/report.html", + }, + }), + ], + })); + + await render(); + + expect(container.textContent).toContain("agent-report.html"); + expect(container.querySelector('a[href="//evil.example/report.html"]')).toBeNull(); + expect(container.querySelector('a[href="https://evil.example/report.html"]')).toBeNull(); + }); + it("orders execution workspace plugin tabs against built-in tabs by slot order", async () => { mockPluginSlotState.slots = [ pluginSlot({ id: "default-tab", displayName: "Default" }), diff --git a/ui/src/pages/ExecutionWorkspaceDetail.tsx b/ui/src/pages/ExecutionWorkspaceDetail.tsx index 17cec596662..afebc017bd4 100644 --- a/ui/src/pages/ExecutionWorkspaceDetail.tsx +++ b/ui/src/pages/ExecutionWorkspaceDetail.tsx @@ -315,7 +315,7 @@ function safeWorkProductHref(workProduct: IssueWorkProduct) { const metadataOpenPath = metadata && typeof metadata.openPath === "string" ? metadata.openPath : null; const candidate = workProduct.url ?? metadataOpenPath; if (!candidate) return null; - if (candidate.startsWith("/")) return candidate; + if (candidate.startsWith("/") && !candidate.startsWith("//")) return candidate; return isSafeExternalUrl(candidate) ? candidate : null; }