diff --git a/CHANGELOG.md b/CHANGELOG.md index e6a5328..f2c13ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ 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 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 diff --git a/package.json b/package.json index 6493f6f..5be8610 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" 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;