From 14141096ee8a2762edf2e075ce797fe94b8631a6 Mon Sep 17 00:00:00 2001 From: soridalac Date: Thu, 13 Aug 2026 12:00:02 -0700 Subject: [PATCH 1/2] fix: resolve binaries from PATH to prevent CWD-based RCE --- src/commands/dev/generate/command.ts | 4 +++- src/commands/dev/generate/flag.ts | 7 ++++--- src/commands/dev/generate/plugin.ts | 4 +++- src/generator.ts | 13 +++++++++++-- test/generator.test.ts | 25 +++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 test/generator.test.ts diff --git a/src/commands/dev/generate/command.ts b/src/commands/dev/generate/command.ts index e3079a56..2f16f050 100644 --- a/src/commands/dev/generate/command.ts +++ b/src/commands/dev/generate/command.ts @@ -107,7 +107,9 @@ export default class GenerateCommand extends SfCommand { if (Object.keys(generator.pjson.devDependencies).includes('@salesforce/plugin-command-reference')) { // Get a list of all commands in `sf`. We will use this to determine if a topic is internal or external. - const sfCommandsStdout = shelljs.exec('sf commands --json', { silent: true }).stdout; + const sfBin = shelljs.which('sf'); + if (!sfBin) throw messages.createError('errors.InvalidDir'); + const sfCommandsStdout = shelljs.exec(`${sfBin} commands --json`, { silent: true }).stdout; const commandsJson = JSON.parse(sfCommandsStdout) as Array<{ id: string }>; const commands = commandsJson.map((command) => command.id.replace(/:/g, '.').replace(/ /g, '.')); diff --git a/src/commands/dev/generate/flag.ts b/src/commands/dev/generate/flag.ts index 7c7c4d98..d0e57107 100644 --- a/src/commands/dev/generate/flag.ts +++ b/src/commands/dev/generate/flag.ts @@ -9,7 +9,6 @@ import path from 'node:path'; import os from 'node:os'; import fs from 'node:fs/promises'; import select from '@inquirer/select'; -import shelljs from 'shelljs'; import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; import { toStandardizedId } from '@oclif/core'; @@ -18,6 +17,7 @@ import { askQuestions } from '../../../prompts/series/flagPrompts.js'; import { fileExists, build, apply, resolveCommandFilePath } from '../../../util.js'; import { FlagAnswers } from '../../../types.js'; import { stringToChoice } from '../../../prompts/functions.js'; +import { Generator } from '../../../generator.js'; Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); export const messages = Messages.loadMessages('@salesforce/plugin-dev', 'dev.generate.flag'); @@ -66,9 +66,10 @@ export default class DevGenerateFlag extends SfCommand { await updateMarkdownFile(answers, existing, standardizedCommandId); - shelljs.exec(`yarn prettier --write ${commandFilePath}`); + const generator = new Generator({ dryRun: flags['dry-run'] }); + generator.execute(`yarn prettier --write ${commandFilePath}`); - shelljs.exec('yarn compile'); + generator.execute('yarn compile'); this.log(`Added ${answers.name} flag to ${commandFilePath}`); } diff --git a/src/commands/dev/generate/plugin.ts b/src/commands/dev/generate/plugin.ts index 02ac6ea7..44fa9aeb 100644 --- a/src/commands/dev/generate/plugin.ts +++ b/src/commands/dev/generate/plugin.ts @@ -49,7 +49,9 @@ async function fetchGithubUserFromAPI(): Promise<{ login: string; name: string } function fetchGithubUserFromGit(): string | undefined { try { - const result = shelljs.exec('git config --get user.name', { silent: true }); + const gitBin = shelljs.which('git'); + if (!gitBin) return undefined; + const result = shelljs.exec(`${gitBin} config --get user.name`, { silent: true }); return result.stdout.trim(); } catch { // ignore diff --git a/src/generator.ts b/src/generator.ts index bc24b4fd..93e51a78 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -88,8 +88,17 @@ export class Generator { return; } - this.logger.debug(`Executing command: ${cmd}`); - shelljs.exec(cmd, { cwd: this.cwd }); + const args = cmd.split(' '); + const bin = args[0]; + const isBinPath = bin.includes('/') || bin.includes('\\'); + const resolved = isBinPath ? bin : shelljs.which(bin); + if (!resolved) { + throw new Error(`Could not find "${bin}" on PATH`); + } + const resolvedCmd = [resolved.toString(), ...args.slice(1)].join(' '); + + this.logger.debug(`Executing command: ${resolvedCmd}`); + shelljs.exec(resolvedCmd, { cwd: this.cwd }); } public async loadPjson(): Promise { diff --git a/test/generator.test.ts b/test/generator.test.ts new file mode 100644 index 00000000..706cc938 --- /dev/null +++ b/test/generator.test.ts @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2024, salesforce.com, inc. + * All rights reserved. + * Licensed under the BSD 3-Clause license. + * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import { expect } from 'chai'; +import { Generator } from '../src/generator.js'; + +describe('Generator.execute', () => { + it('should throw when binary is not found on PATH', () => { + const generator = new Generator(); + + expect(() => generator.execute('nonexistent-binary-xyz --help')).to.throw( + 'Could not find "nonexistent-binary-xyz" on PATH' + ); + }); + + it('should not execute commands in dry-run mode', () => { + const generator = new Generator({ dryRun: true }); + + generator.execute('nonexistent-binary-xyz --help'); + }); +}); From 5c2b2543d9dec3127753157d1222a1718605d021 Mon Sep 17 00:00:00 2001 From: svc-cli-bot Date: Fri, 14 Aug 2026 16:07:44 +0000 Subject: [PATCH 2/2] chore(release): 2.5.3-qa.0 [skip ci] --- README.md | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1ede6105..716962ef 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ FLAG DESCRIPTIONS The default is the "src" directory in the current working directory. ``` -_See code: [src/commands/dev/audit/messages.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.2/src/commands/dev/audit/messages.ts)_ +_See code: [src/commands/dev/audit/messages.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.3-qa.0/src/commands/dev/audit/messages.ts)_ ## `sf dev convert messages` @@ -191,7 +191,7 @@ EXAMPLES $ sf dev convert messages --project-dir ./path/to/plugin --filename my-command.json ``` -_See code: [src/commands/dev/convert/messages.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.2/src/commands/dev/convert/messages.ts)_ +_See code: [src/commands/dev/convert/messages.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.3-qa.0/src/commands/dev/convert/messages.ts)_ ## `sf dev convert script` @@ -235,7 +235,7 @@ EXAMPLES $ sf dev convert script --script ./myScript.yml ``` -_See code: [src/commands/dev/convert/script.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.2/src/commands/dev/convert/script.ts)_ +_See code: [src/commands/dev/convert/script.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.3-qa.0/src/commands/dev/convert/script.ts)_ ## `sf dev generate command` @@ -276,7 +276,7 @@ EXAMPLES $ sf dev generate command --name my:exciting:command ``` -_See code: [src/commands/dev/generate/command.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.2/src/commands/dev/generate/command.ts)_ +_See code: [src/commands/dev/generate/command.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.3-qa.0/src/commands/dev/generate/command.ts)_ ## `sf dev generate flag` @@ -316,7 +316,7 @@ EXAMPLES $ sf dev generate flag --dry-run ``` -_See code: [src/commands/dev/generate/flag.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.2/src/commands/dev/generate/flag.ts)_ +_See code: [src/commands/dev/generate/flag.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.3-qa.0/src/commands/dev/generate/flag.ts)_ ## `sf dev generate plugin` @@ -350,6 +350,6 @@ EXAMPLES $ sf dev generate plugin ``` -_See code: [src/commands/dev/generate/plugin.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.2/src/commands/dev/generate/plugin.ts)_ +_See code: [src/commands/dev/generate/plugin.ts](https://github.com/salesforcecli/plugin-dev/blob/2.5.3-qa.0/src/commands/dev/generate/plugin.ts)_ diff --git a/package.json b/package.json index 69c582d2..0368c24f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@salesforce/plugin-dev", "description": "commands for sf plugin development", - "version": "2.5.2", + "version": "2.5.3-qa.0", "author": "Salesforce", "bugs": "https://github.com/forcedotcom/cli/issues", "dependencies": {