From 5ce3772e9d307b7fc1b50d8754f072b45f8c80c4 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Tue, 10 Nov 2026 14:13:21 +0000 Subject: [PATCH] fix(auth): require consent before applying Requesty deep links --- src/activate/__tests__/handleUri.spec.ts | 54 ++++++++++++++++++++++++ src/activate/handleUri.ts | 18 +++++++- 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/activate/__tests__/handleUri.spec.ts b/src/activate/__tests__/handleUri.spec.ts index c7f7236ebb..a5dec638a8 100644 --- a/src/activate/__tests__/handleUri.spec.ts +++ b/src/activate/__tests__/handleUri.spec.ts @@ -1,6 +1,18 @@ +const { mockShowWarningMessage } = vi.hoisted(() => ({ + mockShowWarningMessage: + vi.fn< + ( + message: string, + options: import("vscode").MessageOptions, + ...items: string[] + ) => Promise + >(), +})) + vi.mock("vscode", () => ({ window: { showInformationMessage: vi.fn(), + showWarningMessage: mockShowWarningMessage, }, })) @@ -53,11 +65,13 @@ const makeCallbackProvider = (callback = vi.fn()): ClineProvider => describe("handleUri", () => { beforeEach(() => { vi.clearAllMocks() + mockShowWarningMessage.mockResolvedValue(undefined) mockGetVisibleInstance.mockReturnValue(mockVisibleProvider) mockGetAllInstances.mockReturnValue([mockVisibleProvider]) }) it.each(["/openrouter", "/requesty"])("dispatches %s codes without losing literal plus signs", async (path) => { + mockShowWarningMessage.mockImplementation(async () => "Activate Requesty") await handleUri(makeUri({ path, query: "code=a+b%2Bc&baseUrl=https%3A%2F%2Fexample.com" })) if (path === "/openrouter") { @@ -70,7 +84,47 @@ describe("handleUri", () => { }) it("passes null when Requesty has no base URL", async () => { + mockShowWarningMessage.mockImplementation(async () => "Activate Requesty") await handleUri(makeUri({ path: "/requesty", query: "code=test-code" })) + expect(vscode.window.showWarningMessage).toHaveBeenCalledWith( + "Activate Requesty credentials from an external link?", + expect.objectContaining({ modal: true, detail: expect.stringContaining("https://router.requesty.ai/v1") }), + "Activate Requesty", + ) + expect(mockVisibleProvider.handleRequestyCallback).toHaveBeenCalledWith("test-code", null) + }) + + it.each([undefined, "Cancel"])( + "does not apply an unsolicited Requesty callback when dismissed with %s", + async (choice) => { + mockShowWarningMessage.mockImplementation(async () => choice) + await handleUri( + makeUri({ path: "/requesty", query: "code=attacker-key&baseUrl=https://attacker.example/v1" }), + ) + expect(vscode.window.showWarningMessage).toHaveBeenCalledWith( + "Activate Requesty credentials from an external link?", + expect.objectContaining({ + modal: true, + detail: expect.stringContaining("https://attacker.example/v1"), + }), + "Activate Requesty", + ) + expect(mockVisibleProvider.handleRequestyCallback).not.toHaveBeenCalled() + }, + ) + + it("waits for explicit approval before applying callback credentials", async () => { + let approve: (choice: string | undefined) => void = () => {} + mockShowWarningMessage.mockImplementation( + () => + new Promise((resolve) => { + approve = resolve + }), + ) + const callback = handleUri(makeUri({ path: "/requesty", query: "code=test-code" })) + expect(mockVisibleProvider.handleRequestyCallback).not.toHaveBeenCalled() + approve("Activate Requesty") + await callback expect(mockVisibleProvider.handleRequestyCallback).toHaveBeenCalledWith("test-code", null) }) diff --git a/src/activate/handleUri.ts b/src/activate/handleUri.ts index cf0fc69175..2f04544d11 100644 --- a/src/activate/handleUri.ts +++ b/src/activate/handleUri.ts @@ -1,5 +1,7 @@ import * as vscode from "vscode" +import { toRequestyServiceUrl } from "../shared/utils/requesty" + import { getRouterUnavailableSignInMessage } from "../core/config/routerRemoval" import { ClineProvider } from "../core/webview/ClineProvider" import { handleAuthCallback as handleZooCodeAuthCallback, setZooCodeUserInfo } from "../services/zoo-code-auth" @@ -51,7 +53,21 @@ export const handleUri = async (uri: vscode.Uri) => { const code = query.get("code") const baseUrl = query.get("baseUrl") if (code) { - await visibleProvider.handleRequestyCallback(code, baseUrl) + // Deep links are untrusted. Require consent before installing credentials + // or redirecting an existing task's conversation to this destination. + const destination = toRequestyServiceUrl(baseUrl) + const approve = "Activate Requesty" + const choice = await vscode.window.showWarningMessage( + "Activate Requesty credentials from an external link?", + { + modal: true, + detail: `This will replace the active provider. Subsequent requests can send your conversation and workspace content to:\n${JSON.stringify(destination)}\n\nContinue only if you requested these credentials and trust this destination.`, + }, + approve, + ) + if (choice === approve) { + await visibleProvider.handleRequestyCallback(code, baseUrl) + } } break }