From 9ebd7df3c1a4ac99c4e019f9f028c2655a2c4702 Mon Sep 17 00:00:00 2001 From: Dung Dong Date: Thu, 29 Oct 2026 20:20:42 +0000 Subject: [PATCH] fix(amazonq): open only http and https links from chat --- .../amazonqScan/chat/controller/controller.ts | 4 +-- packages/amazonq/src/lsp/chat/messages.ts | 4 +-- .../webview/messages/messageDispatcher.ts | 5 ++- .../chat/controller/controller.ts | 4 +-- packages/core/src/shared/index.ts | 2 +- .../core/src/shared/utilities/vsCodeUtils.ts | 22 +++++++++++++ .../test/shared/utilities/vscodeUtils.test.ts | 33 +++++++++++++++++++ 7 files changed, 64 insertions(+), 10 deletions(-) diff --git a/packages/amazonq/src/app/amazonqScan/chat/controller/controller.ts b/packages/amazonq/src/app/amazonqScan/chat/controller/controller.ts index 72af0a200c..884335f4fa 100644 --- a/packages/amazonq/src/app/amazonqScan/chat/controller/controller.ts +++ b/packages/amazonq/src/app/amazonqScan/chat/controller/controller.ts @@ -7,7 +7,7 @@ */ import * as vscode from 'vscode' import { AuthController } from 'aws-core-vscode/amazonq' -import { getLogger, placeholder, i18n, openUrl, fs, TabTypeDataMap, randomUUID } from 'aws-core-vscode/shared' +import { getLogger, placeholder, i18n, openWebLink, fs, TabTypeDataMap, randomUUID } from 'aws-core-vscode/shared' import { ScanChatControllerEventEmitters, Session, ChatSessionManager } from 'aws-core-vscode/amazonqScan' import { AggregatedCodeScanIssue, @@ -344,7 +344,7 @@ export class ScanController { } private processLink(message: any) { - void openUrl(vscode.Uri.parse(message.link)) + void openWebLink(message.link) } private async processFileClick(message: any) { diff --git a/packages/amazonq/src/lsp/chat/messages.ts b/packages/amazonq/src/lsp/chat/messages.ts index 6309ffc963..6100bdb7b1 100644 --- a/packages/amazonq/src/lsp/chat/messages.ts +++ b/packages/amazonq/src/lsp/chat/messages.ts @@ -82,7 +82,7 @@ import { SecurityIssueTreeViewProvider, CodeWhispererConstants, } from 'aws-core-vscode/codewhisperer' -import { AmazonQPromptSettings, messages, openUrl, isTextEditor, globals, setContext } from 'aws-core-vscode/shared' +import { AmazonQPromptSettings, messages, openWebLink, isTextEditor, globals, setContext } from 'aws-core-vscode/shared' import { DefaultAmazonQAppInitContext, messageDispatcher, referenceLogText } from 'aws-core-vscode/amazonq' import { telemetry } from 'aws-core-vscode/telemetry' import { isValidResponseError } from './error' @@ -267,7 +267,7 @@ export function registerMessageListeners( case INFO_LINK_CLICK_NOTIFICATION_METHOD: case LINK_CLICK_NOTIFICATION_METHOD: { const linkParams = message.params as LinkClickParams - void openUrl(vscode.Uri.parse(linkParams.link)) + void openWebLink(linkParams.link) break } case STOP_CHAT_RESPONSE: { diff --git a/packages/core/src/amazonq/webview/messages/messageDispatcher.ts b/packages/core/src/amazonq/webview/messages/messageDispatcher.ts index 38f583c35e..3c6af4bb74 100644 --- a/packages/core/src/amazonq/webview/messages/messageDispatcher.ts +++ b/packages/core/src/amazonq/webview/messages/messageDispatcher.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Uri } from 'vscode' import { MessagePublisher } from '../../messages/messagePublisher' import { TabType } from '../ui/storages/tabsStorage' import { amazonqMark } from '../../../shared/performance/marks' @@ -11,7 +10,7 @@ import { telemetry } from '../../../shared/telemetry/telemetry' import { AmazonQChatMessageDuration } from '../../messages/chatMessageDuration' import { isClickTelemetry, isOpenAgentTelemetry } from '../ui/telemetry/actions' import globals from '../../../shared/extensionGlobals' -import { openUrl } from '../../../shared/utilities/vsCodeUtils' +import { openWebLink } from '../../../shared/utilities/vsCodeUtils' import { DefaultAmazonQAppInitContext } from '../../apps/initContext' import { AmazonQPromptSettings } from '../../../shared/settings' @@ -64,7 +63,7 @@ export function handleWebviewEvent(msg: any, webViewToAppsMessagePublishers: Map } case 'open-link': { const { link } = msg - void openUrl(Uri.parse(link)) + void openWebLink(link) return } case 'send-telemetry': { diff --git a/packages/core/src/amazonqGumby/chat/controller/controller.ts b/packages/core/src/amazonqGumby/chat/controller/controller.ts index 5d21c6b77f..2e53cf5d40 100644 --- a/packages/core/src/amazonqGumby/chat/controller/controller.ts +++ b/packages/core/src/amazonqGumby/chat/controller/controller.ts @@ -42,7 +42,7 @@ import { import * as CodeWhispererConstants from '../../../codewhisperer/models/constants' import MessengerUtils, { ButtonActions, GumbyCommands } from './messenger/messengerUtils' import { CancelActionPositions } from '../../telemetry/codeTransformTelemetry' -import { openUrl } from '../../../shared/utilities/vsCodeUtils' +import { openWebLink } from '../../../shared/utilities/vsCodeUtils' import { telemetry } from '../../../shared/telemetry/telemetry' import { CodeTransformTelemetryState } from '../../telemetry/codeTransformTelemetryState' import DependencyVersions from '../../models/dependencies' @@ -732,7 +732,7 @@ export class GumbyController { } private openLink(message: { link: string }) { - void openUrl(vscode.Uri.parse(message.link)) + void openWebLink(message.link) } private async handleError(message: { error: Error; tabID: string }) { diff --git a/packages/core/src/shared/index.ts b/packages/core/src/shared/index.ts index 8b62fd3c5d..c99775c9b3 100644 --- a/packages/core/src/shared/index.ts +++ b/packages/core/src/shared/index.ts @@ -20,7 +20,7 @@ export { RegionProvider } from './regions/regionProvider' export { Commands } from './vscode/commands2' export { getMachineId, getServiceEnvVarConfig, isAmazonLinux2 } from './vscode/env' export { getLogger } from './logger/logger' -export { activateExtension, openUrl } from './utilities/vsCodeUtils' +export { activateExtension, openUrl, openWebLink } from './utilities/vsCodeUtils' export { waitUntil, sleep, Timeout } from './utilities/timeoutUtils' export * as timeoutUtils from './utilities/timeoutUtils' export { Prompter } from './ui/prompter' diff --git a/packages/core/src/shared/utilities/vsCodeUtils.ts b/packages/core/src/shared/utilities/vsCodeUtils.ts index 57f9e38097..969eb60f1b 100644 --- a/packages/core/src/shared/utilities/vsCodeUtils.ts +++ b/packages/core/src/shared/utilities/vsCodeUtils.ts @@ -229,6 +229,28 @@ export async function openUrl(url: vscode.Uri, source?: string): Promise { + let url: vscode.Uri | undefined + try { + url = vscode.Uri.parse(link, true) + } catch { + url = undefined + } + const scheme = url?.scheme.toLowerCase() + if (!url || (scheme !== 'http' && scheme !== 'https') || !url.authority) { + getLogger().warn('openWebLink: ignored link with scheme "%s", only http(s) links are opened', scheme ?? '') + return false + } + return openUrl(url, source) +} + export function isToolkitActive(): boolean { return isExtensionActive(VSCODE_EXTENSION_ID.awstoolkit) } diff --git a/packages/core/src/test/shared/utilities/vscodeUtils.test.ts b/packages/core/src/test/shared/utilities/vscodeUtils.test.ts index 450a378589..e9d38fdd77 100644 --- a/packages/core/src/test/shared/utilities/vscodeUtils.test.ts +++ b/packages/core/src/test/shared/utilities/vscodeUtils.test.ts @@ -8,6 +8,7 @@ import { VSCODE_EXTENSION_ID } from '../../../shared/extensions' import * as vscodeUtil from '../../../shared/utilities/vsCodeUtils' import * as vscode from 'vscode' import { getExcludePattern } from '../../../shared/fs/watchedFiles' +import { getOpenExternalStub } from '../../globalSetup.test' describe('vscodeUtils', async function () { it('activateExtension(), isExtensionActive()', async function () { @@ -119,3 +120,35 @@ describe('buildMissingExtensionMessage()', function () { ) }) }) + +describe('openWebLink()', function () { + beforeEach(function () { + getOpenExternalStub().resolves(true) + }) + + for (const link of ['https://aws.amazon.com/q/', 'http://example.com/path?a=1#b', 'HTTPS://example.com']) { + it(`opens ${link}`, async function () { + assert.strictEqual(await vscodeUtil.openWebLink(link), true) + assert.ok(getOpenExternalStub().calledOnce) + assert.strictEqual(getOpenExternalStub().args[0][0].toString(), vscode.Uri.parse(link).toString()) + }) + } + + for (const link of [ + '//host/x', + '//host/share/x.exe', + 'file:///etc/passwd', + '\\\\host\\share', + 'vscode://x', + 'command:x', + 'foo/bar', + '/foo/bar', + 'https:foo', + '', + ]) { + it(`does not open ${JSON.stringify(link)}`, async function () { + assert.strictEqual(await vscodeUtil.openWebLink(link), false) + assert.ok(getOpenExternalStub().notCalled) + }) + } +})