From bb9cd4673dc344156a89a99cde9939666d7caef4 Mon Sep 17 00:00:00 2001 From: Uzoma Nwakuche Date: Mon, 28 Sep 2026 23:22:29 +0000 Subject: [PATCH 1/4] fix(codeRefs): replace exec() with execFile() to prevent command injection Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- CHANGELOG.md | 22 +++++++++++++++------- package.json | 12 +++++++++++- src/models.ts | 2 +- src/providers/codeRefs.ts | 28 +++++++++++++++++++++------- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6a5328..ed9cf48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to the "launchdarkly" extension will be documented in this file. +## [6.2.2] - 2026-09-28 + +### Security + +- Fixed a command injection vulnerability in Code References alias generation. The `ld-find-code-refs` binary is now invoked with `execFile` and an argument array instead of a shell command string, so workspace-controlled values (`launchdarkly.project`, workspace folder paths) can no longer inject shell commands or expose `LD_ACCESS_TOKEN`. +- Declared `untrustedWorkspaces` support as `limited`, restricting workspace values for `launchdarkly.project` and `launchdarkly.codeRefsPath` in Restricted Mode. ## [6.2.1] - 2026-09-07 @@ -16,26 +22,26 @@ All notable changes to the "launchdarkly" extension will be documented in this f - Added "EU" as a first-class option in the instance picker during sign-in, making it easier for EU customers to connect to `app.eu.launchdarkly.com` ## [6.1.0] - 2025-12-05 -- Bumping major and minor version to address rogue published version that is not captured in our package.json +- Bumping major and minor version to address rogue published version that is not captured in our package.json ## [5.1.0] - 2025-09-01 - + ### Fixed - Bug with token validation -- Updated broken quick link urls -- Upgraded failing dependencies +- Updated broken quick link urls +- Upgraded failing dependencies ### Changed - Login flow -- Better handling of invalid sessions +- Better handling of invalid sessions -### Added +### Added - Ability to sign out of session -- Custom error handling for failed authentication +- Custom error handling for failed authentication - Developement instructions into README ## [5.0.0] - 2024-02-06 @@ -46,10 +52,12 @@ All notable changes to the "launchdarkly" extension will be documented in this f - Initial setup now recovers better if there's been a problem. ## Changed + - Initial setup is now split into Sign in and Configuration. - Updated CLI version of Code References. ## Added + - Code lens and hover now show SDK availability of a flag. - Sign via through AuthProvider API. - `LaunchDarkly: Flag Actions` command. diff --git a/package.json b/package.json index 6493f6f..7b61d20 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "launchdarkly", "displayName": "LaunchDarkly", "description": "Manage LaunchDarkly feature flags directly from within your code", - "version": "6.2.1", + "version": "6.2.2", "publisher": "launchdarklyofficial", "engines": { "vscode": "^1.82.0" @@ -17,6 +17,16 @@ "onStartupFinished" ], "main": "./dist/extension", + "capabilities": { + "untrustedWorkspaces": { + "supported": "limited", + "description": "The Code References feature runs an external binary with workspace-provided paths and settings. It is disabled in untrusted workspaces.", + "restrictedConfigurations": [ + "launchdarkly.project", + "launchdarkly.codeRefsPath" + ] + } + }, "contributes": { "icons": { "launchdarkly-logo": { diff --git a/src/models.ts b/src/models.ts index 199e530..0dd5368 100644 --- a/src/models.ts +++ b/src/models.ts @@ -640,7 +640,7 @@ export interface IFlagAliases { aliasUpdates: EventEmitter; codeRefsVersionCheck(): Promise; setupStatusBar(): void; - exec(command: string, options: ExecOptions): Promise<{ stdout: string; stderr: string }>; + exec(file: string, args: string[], options: ExecOptions): Promise<{ stdout: string; stderr: string }>; generateAndReadAliases(directory?: WorkspaceFolder): Promise; getListOfMapKeys(): Array | undefined; getMap(): Map | undefined; diff --git a/src/providers/codeRefs.ts b/src/providers/codeRefs.ts index a0b4834..7bd4553 100644 --- a/src/providers/codeRefs.ts +++ b/src/providers/codeRefs.ts @@ -1,5 +1,5 @@ import { EventEmitter, ExtensionContext, StatusBarAlignment, StatusBarItem, window, workspace } from 'vscode'; -import { exec, ExecOptions } from 'child_process'; +import { execFile, ExecOptions } from 'child_process'; import { createReadStream } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; @@ -54,9 +54,9 @@ export class FlagAliases { } } - exec(command: string, options: ExecOptions): Promise<{ stdout: string; stderr: string }> { + exec(file: string, args: string[], options: ExecOptions): Promise<{ stdout: string; stderr: string }> { return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => { - exec(command, options, (error, stdout, stderr) => { + execFile(file, args, options, (error, stdout, stderr) => { if (error) { reject({ error, stdout: stdout.toString(), stderr: stderr.toString() }); } @@ -110,8 +110,23 @@ export class FlagAliases { const apiToken = legacyAuth() ? session.accessToken : `Bearer ${session.accessToken}`; try { const codeRefsBin = await this.getCodeRefsBin(); - const command = `${codeRefsBin} --dir="${directory}" --dryRun --outDir="${outDir}" --projKey="${intConfig.project}" --repoName="${repoName}" --baseUri="${session.fullUri}" --contextLines=-1 --branch=scan --revision=0`; - const output = await this.exec(command, { + const args = [ + '--dir', + directory, + '--dryRun', + '--outDir', + outDir, + '--projKey', + intConfig.project, + '--repoName', + repoName, + '--baseUri', + session.fullUri, + '--contextLines=-1', + '--branch=scan', + '--revision=0', + ]; + const output = await this.exec(codeRefsBin, args, { env: { LD_ACCESS_TOKEN: apiToken, GOMAXPROCS: '1' }, timeout: 20 * 60000, }); @@ -185,8 +200,7 @@ export class FlagAliases { if (!codeRefsBin) { return false; } - const command = `${codeRefsBin} --version`; - const output = await this.exec(command, {}); + const output = await this.exec(codeRefsBin, ['--version'], {}); if (output.stderr) { window.showErrorMessage(output.stderr); return false; From bfea5630d9142408557fca900fa34841b4670196 Mon Sep 17 00:00:00 2001 From: Uzoma Nwakuche Date: Mon, 28 Sep 2026 23:29:56 +0000 Subject: [PATCH 2/4] fix(codeRefs): skip Code References in untrusted workspaces Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 +- src/providers/codeRefs.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed9cf48..630a90c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to the "launchdarkly" extension will be documented in this f ### Security - Fixed a command injection vulnerability in Code References alias generation. The `ld-find-code-refs` binary is now invoked with `execFile` and an argument array instead of a shell command string, so workspace-controlled values (`launchdarkly.project`, workspace folder paths) can no longer inject shell commands or expose `LD_ACCESS_TOKEN`. -- Declared `untrustedWorkspaces` support as `limited`, restricting workspace values for `launchdarkly.project` and `launchdarkly.codeRefsPath` in Restricted Mode. +- Declared `untrustedWorkspaces` support as `limited`: Code References is disabled in untrusted workspaces, and workspace values for `launchdarkly.project` and `launchdarkly.codeRefsPath` are restricted in Restricted Mode. ## [6.2.1] - 2026-09-07 diff --git a/src/providers/codeRefs.ts b/src/providers/codeRefs.ts index 7bd4553..1e7ba15 100644 --- a/src/providers/codeRefs.ts +++ b/src/providers/codeRefs.ts @@ -38,6 +38,9 @@ export class FlagAliases { aliases: Array; async start(): Promise { + if (!workspace.isTrusted) { + return; + } const intConfig = this.config?.getConfig(); if (intConfig && (await intConfig.isConfigured())) { const aliasFile = await workspace.findFiles('.launchdarkly/coderefs.yaml'); @@ -140,7 +143,7 @@ export class FlagAliases { } async generateAndReadAliases(directory = workspace.workspaceFolders?.[0] ?? null): Promise { - if (!directory || !this.config.getSession()) { + if (!workspace.isTrusted || !directory || !this.config.getSession()) { return; } const refsDir = directory.uri.fsPath; @@ -195,6 +198,9 @@ export class FlagAliases { } async codeRefsVersionCheck(): Promise { + if (!workspace.isTrusted) { + return false; + } try { const codeRefsBin = await this.getCodeRefsBin(); if (!codeRefsBin) { From 6bbadcf47346b99668ded5a8b88ae5120726c633 Mon Sep 17 00:00:00 2001 From: Uzoma Nwakuche Date: Mon, 28 Sep 2026 23:35:21 +0000 Subject: [PATCH 3/4] fix(codeRefs): start Code References when workspace trust is granted Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/generalUtils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/generalUtils.ts b/src/generalUtils.ts index 43eeb66..10b2b08 100644 --- a/src/generalUtils.ts +++ b/src/generalUtils.ts @@ -90,6 +90,9 @@ export async function setupComponents(config: ILDExtensionConfiguration, reload if (config.getAliases().codeRefsVersionCheck()) { config.getAliases().setupStatusBar(); await config.getAliases().start(); + if (!workspace.isTrusted) { + config.getCtx().subscriptions.push(workspace.onDidGrantWorkspaceTrust(() => config.getAliases()?.start())); + } } else { window.showErrorMessage('ld-find-code-refs version > 2 supported.'); } From f356deb8551adc9b8289efb72a56f7ae3a78d258 Mon Sep 17 00:00:00 2001 From: Uzoma Nwakuche Date: Mon, 28 Sep 2026 23:51:36 -0700 Subject: [PATCH 4/4] fix(codeRefs): drop untrustedWorkspaces declaration, keep execFile fix The execFile() change fully remediates the reported command injection. The Workspace Trust additions are removed because: - Declaring untrustedWorkspaces "limited" widened exposure. Without a declaration VS Code disables the extension entirely in Restricted Mode. With "limited" the whole extension (auth session, API client, flag store, sign-in flow) activated in untrusted workspaces, with only the Code References paths gated. - restrictedConfigurations was inert. VS Code only applies it to settings registered under contributes.configuration, and neither launchdarkly.project nor launchdarkly.codeRefsPath is registered. - The workspace.isTrusted guards and onDidGrantWorkspaceTrust handler are dead code without the declaration: the extension never runs in an untrusted workspace, and VS Code activates it itself when trust is granted. Also restores the unrelated CHANGELOG whitespace normalization and narrows the 6.2.2 entry to the execFile fix. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 19 ++++++++----------- package.json | 10 ---------- src/generalUtils.ts | 3 --- src/providers/codeRefs.ts | 8 +------- 4 files changed, 9 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 630a90c..f2c13ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,7 @@ All notable changes to the "launchdarkly" extension will be documented in this f ### Security -- Fixed a command injection vulnerability in Code References alias generation. The `ld-find-code-refs` binary is now invoked with `execFile` and an argument array instead of a shell command string, so workspace-controlled values (`launchdarkly.project`, workspace folder paths) can no longer inject shell commands or expose `LD_ACCESS_TOKEN`. -- Declared `untrustedWorkspaces` support as `limited`: Code References is disabled in untrusted workspaces, and workspace values for `launchdarkly.project` and `launchdarkly.codeRefsPath` are restricted in Restricted Mode. +- Fixed a command injection vulnerability in Code References alias generation. The extension now runs `ld-find-code-refs` with `execFile` and an argument array instead of a shell command string. Values that come from the workspace, such as `launchdarkly.project` and the workspace folder path, can no longer run shell commands or read `LD_ACCESS_TOKEN`. ## [6.2.1] - 2026-09-07 @@ -22,26 +21,26 @@ All notable changes to the "launchdarkly" extension will be documented in this f - Added "EU" as a first-class option in the instance picker during sign-in, making it easier for EU customers to connect to `app.eu.launchdarkly.com` ## [6.1.0] - 2025-12-05 - - Bumping major and minor version to address rogue published version that is not captured in our package.json -## [5.1.0] - 2025-09-01 +## [5.1.0] - 2025-09-01 + ### Fixed - Bug with token validation -- Updated broken quick link urls -- Upgraded failing dependencies +- Updated broken quick link urls +- Upgraded failing dependencies ### Changed - Login flow -- Better handling of invalid sessions +- Better handling of invalid sessions -### Added +### Added - Ability to sign out of session -- Custom error handling for failed authentication +- Custom error handling for failed authentication - Developement instructions into README ## [5.0.0] - 2024-02-06 @@ -52,12 +51,10 @@ All notable changes to the "launchdarkly" extension will be documented in this f - Initial setup now recovers better if there's been a problem. ## Changed - - Initial setup is now split into Sign in and Configuration. - Updated CLI version of Code References. ## Added - - Code lens and hover now show SDK availability of a flag. - Sign via through AuthProvider API. - `LaunchDarkly: Flag Actions` command. diff --git a/package.json b/package.json index 7b61d20..5be8610 100644 --- a/package.json +++ b/package.json @@ -17,16 +17,6 @@ "onStartupFinished" ], "main": "./dist/extension", - "capabilities": { - "untrustedWorkspaces": { - "supported": "limited", - "description": "The Code References feature runs an external binary with workspace-provided paths and settings. It is disabled in untrusted workspaces.", - "restrictedConfigurations": [ - "launchdarkly.project", - "launchdarkly.codeRefsPath" - ] - } - }, "contributes": { "icons": { "launchdarkly-logo": { diff --git a/src/generalUtils.ts b/src/generalUtils.ts index 10b2b08..43eeb66 100644 --- a/src/generalUtils.ts +++ b/src/generalUtils.ts @@ -90,9 +90,6 @@ export async function setupComponents(config: ILDExtensionConfiguration, reload if (config.getAliases().codeRefsVersionCheck()) { config.getAliases().setupStatusBar(); await config.getAliases().start(); - if (!workspace.isTrusted) { - config.getCtx().subscriptions.push(workspace.onDidGrantWorkspaceTrust(() => config.getAliases()?.start())); - } } else { window.showErrorMessage('ld-find-code-refs version > 2 supported.'); } diff --git a/src/providers/codeRefs.ts b/src/providers/codeRefs.ts index 1e7ba15..7bd4553 100644 --- a/src/providers/codeRefs.ts +++ b/src/providers/codeRefs.ts @@ -38,9 +38,6 @@ export class FlagAliases { aliases: Array; async start(): Promise { - if (!workspace.isTrusted) { - return; - } const intConfig = this.config?.getConfig(); if (intConfig && (await intConfig.isConfigured())) { const aliasFile = await workspace.findFiles('.launchdarkly/coderefs.yaml'); @@ -143,7 +140,7 @@ export class FlagAliases { } async generateAndReadAliases(directory = workspace.workspaceFolders?.[0] ?? null): Promise { - if (!workspace.isTrusted || !directory || !this.config.getSession()) { + if (!directory || !this.config.getSession()) { return; } const refsDir = directory.uri.fsPath; @@ -198,9 +195,6 @@ export class FlagAliases { } async codeRefsVersionCheck(): Promise { - if (!workspace.isTrusted) { - return false; - } try { const codeRefsBin = await this.getCodeRefsBin(); if (!codeRefsBin) {