From 41bf3252d925d2e5d521bb8916dd2b661b60e852 Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Fri, 18 Jul 2025 11:15:21 +0200 Subject: [PATCH] Fix command injection --- src/shell-integration/helpers.js | 10 +--------- .../supported-shells/bash.js | 19 ++++++++++++++++--- .../supported-shells/bash.spec.js | 8 +++++++- .../supported-shells/fish.js | 19 ++++++++++++++++--- .../supported-shells/fish.spec.js | 8 +++++++- .../supported-shells/powershell.js | 19 ++++++++++++++++--- .../supported-shells/powershell.spec.js | 8 +++++++- .../supported-shells/windowsPowershell.js | 19 ++++++++++++++++--- .../windowsPowershell.spec.js | 8 +++++++- src/shell-integration/supported-shells/zsh.js | 19 ++++++++++++++++--- .../supported-shells/zsh.spec.js | 8 +++++++- 11 files changed, 116 insertions(+), 29 deletions(-) diff --git a/src/shell-integration/helpers.js b/src/shell-integration/helpers.js index 4226801..9075e66 100644 --- a/src/shell-integration/helpers.js +++ b/src/shell-integration/helpers.js @@ -1,4 +1,4 @@ -import { execSync, spawnSync } from "child_process"; +import { spawnSync } from "child_process"; import * as os from "os"; import fs from "fs"; @@ -26,14 +26,6 @@ export function doesExecutableExistOnSystem(executableName) { } } -export function execAndGetOutput(command, shell) { - try { - return execSync(command, { encoding: "utf8", shell }).trim(); - } catch (error) { - throw new Error(`Command failed: ${command}. Error: ${error.message}`); - } -} - export function removeLinesMatchingPattern(filePath, pattern) { if (!fs.existsSync(filePath)) { return; diff --git a/src/shell-integration/supported-shells/bash.js b/src/shell-integration/supported-shells/bash.js index 35d23c1..fd18903 100644 --- a/src/shell-integration/supported-shells/bash.js +++ b/src/shell-integration/supported-shells/bash.js @@ -1,9 +1,9 @@ import { addLineToFile, doesExecutableExistOnSystem, - execAndGetOutput, removeLinesMatchingPattern, } from "../helpers.js"; +import { execSync } from "child_process"; const shellName = "Bash"; const executableName = "bash"; @@ -14,7 +14,7 @@ function isInstalled() { } function teardown() { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); // Removes all aliases starting with "alias npm=", "alias npx=", or "alias yarn=" // This will remove the safe-chain aliases for npm, npx, and yarn commands. @@ -24,7 +24,7 @@ function teardown() { } function setup(tools) { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); teardown(); for (const { tool, aikidoCommand } of tools) { @@ -37,6 +37,19 @@ function setup(tools) { return true; } +function getStartupFile() { + try { + return execSync(startupFileCommand, { + encoding: "utf8", + shell: executableName, + }).trim(); + } catch (error) { + throw new Error( + `Command failed: ${startupFileCommand}. Error: ${error.message}` + ); + } +} + export default { name: shellName, isInstalled, diff --git a/src/shell-integration/supported-shells/bash.spec.js b/src/shell-integration/supported-shells/bash.spec.js index 21be84e..c852a85 100644 --- a/src/shell-integration/supported-shells/bash.spec.js +++ b/src/shell-integration/supported-shells/bash.spec.js @@ -15,7 +15,6 @@ describe("Bash shell integration", () => { // Mock the helpers module mock.module("../helpers.js", { namedExports: { - execAndGetOutput: () => mockStartupFile, doesExecutableExistOnSystem: () => true, addLineToFile: (filePath, line) => { if (!fs.existsSync(filePath)) { @@ -33,6 +32,13 @@ describe("Bash shell integration", () => { }, }); + // Mock child_process execSync + mock.module("child_process", { + namedExports: { + execSync: () => mockStartupFile, + }, + }); + // Import bash module after mocking bash = (await import("./bash.js")).default; }); diff --git a/src/shell-integration/supported-shells/fish.js b/src/shell-integration/supported-shells/fish.js index 429d351..9269262 100644 --- a/src/shell-integration/supported-shells/fish.js +++ b/src/shell-integration/supported-shells/fish.js @@ -1,9 +1,9 @@ import { addLineToFile, doesExecutableExistOnSystem, - execAndGetOutput, removeLinesMatchingPattern, } from "../helpers.js"; +import { execSync } from "child_process"; const shellName = "Fish"; const executableName = "fish"; @@ -14,7 +14,7 @@ function isInstalled() { } function teardown() { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); // Removes all aliases starting with "alias npm=", "alias npx=", or "alias yarn=" // This will remove the safe-chain aliases for npm, npx, and yarn commands. @@ -24,7 +24,7 @@ function teardown() { } function setup(tools) { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); teardown(); for (const { tool, aikidoCommand } of tools) { @@ -37,6 +37,19 @@ function setup(tools) { return true; } +function getStartupFile() { + try { + return execSync(startupFileCommand, { + encoding: "utf8", + shell: executableName, + }).trim(); + } catch (error) { + throw new Error( + `Command failed: ${startupFileCommand}. Error: ${error.message}` + ); + } +} + export default { name: shellName, isInstalled, diff --git a/src/shell-integration/supported-shells/fish.spec.js b/src/shell-integration/supported-shells/fish.spec.js index 15344a3..988b24f 100644 --- a/src/shell-integration/supported-shells/fish.spec.js +++ b/src/shell-integration/supported-shells/fish.spec.js @@ -15,7 +15,6 @@ describe("Fish shell integration", () => { // Mock the helpers module mock.module("../helpers.js", { namedExports: { - execAndGetOutput: () => mockStartupFile, doesExecutableExistOnSystem: () => true, addLineToFile: (filePath, line) => { if (!fs.existsSync(filePath)) { @@ -33,6 +32,13 @@ describe("Fish shell integration", () => { } }); + // Mock child_process execSync + mock.module("child_process", { + namedExports: { + execSync: () => mockStartupFile, + }, + }); + // Import fish module after mocking fish = (await import("./fish.js")).default; }); diff --git a/src/shell-integration/supported-shells/powershell.js b/src/shell-integration/supported-shells/powershell.js index f07efbf..f83093e 100644 --- a/src/shell-integration/supported-shells/powershell.js +++ b/src/shell-integration/supported-shells/powershell.js @@ -1,9 +1,9 @@ import { addLineToFile, doesExecutableExistOnSystem, - execAndGetOutput, removeLinesMatchingPattern, } from "../helpers.js"; +import { execSync } from "child_process"; const shellName = "PowerShell Core"; const executableName = "pwsh"; @@ -14,7 +14,7 @@ function isInstalled() { } function teardown() { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); // Removes all aliases starting with "Set-Alias npm=", "Set-Alias npx=", or "Set-Alias yarn=" // This will remove the safe-chain aliases for npm, npx, and yarn commands. @@ -24,7 +24,7 @@ function teardown() { } function setup(tools) { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); teardown(); for (const { tool, aikidoCommand } of tools) { @@ -37,6 +37,19 @@ function setup(tools) { return true; } +function getStartupFile() { + try { + return execSync(startupFileCommand, { + encoding: "utf8", + shell: executableName, + }).trim(); + } catch (error) { + throw new Error( + `Command failed: ${startupFileCommand}. Error: ${error.message}` + ); + } +} + export default { name: shellName, isInstalled, diff --git a/src/shell-integration/supported-shells/powershell.spec.js b/src/shell-integration/supported-shells/powershell.spec.js index 9d71d94..4006b13 100644 --- a/src/shell-integration/supported-shells/powershell.spec.js +++ b/src/shell-integration/supported-shells/powershell.spec.js @@ -15,7 +15,6 @@ describe("PowerShell Core shell integration", () => { // Mock the helpers module mock.module("../helpers.js", { namedExports: { - execAndGetOutput: () => mockStartupFile, doesExecutableExistOnSystem: () => true, addLineToFile: (filePath, line) => { if (!fs.existsSync(filePath)) { @@ -33,6 +32,13 @@ describe("PowerShell Core shell integration", () => { } }); + // Mock child_process execSync + mock.module("child_process", { + namedExports: { + execSync: () => mockStartupFile, + }, + }); + // Import powershell module after mocking powershell = (await import("./powershell.js")).default; }); diff --git a/src/shell-integration/supported-shells/windowsPowershell.js b/src/shell-integration/supported-shells/windowsPowershell.js index 381b987..584a447 100644 --- a/src/shell-integration/supported-shells/windowsPowershell.js +++ b/src/shell-integration/supported-shells/windowsPowershell.js @@ -1,9 +1,9 @@ import { addLineToFile, doesExecutableExistOnSystem, - execAndGetOutput, removeLinesMatchingPattern, } from "../helpers.js"; +import { execSync } from "child_process"; const shellName = "Windows PowerShell"; const executableName = "powershell"; @@ -14,7 +14,7 @@ function isInstalled() { } function teardown() { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); // Removes all aliases starting with "Set-Alias npm=", "Set-Alias npx=", or "Set-Alias yarn=" // This will remove the safe-chain aliases for npm, npx, and yarn commands. @@ -24,7 +24,7 @@ function teardown() { } function setup(tools) { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); teardown(); for (const { tool, aikidoCommand } of tools) { @@ -37,6 +37,19 @@ function setup(tools) { return true; } +function getStartupFile() { + try { + return execSync(startupFileCommand, { + encoding: "utf8", + shell: executableName, + }).trim(); + } catch (error) { + throw new Error( + `Command failed: ${startupFileCommand}. Error: ${error.message}` + ); + } +} + export default { name: shellName, isInstalled, diff --git a/src/shell-integration/supported-shells/windowsPowershell.spec.js b/src/shell-integration/supported-shells/windowsPowershell.spec.js index fe8b64f..f8dd182 100644 --- a/src/shell-integration/supported-shells/windowsPowershell.spec.js +++ b/src/shell-integration/supported-shells/windowsPowershell.spec.js @@ -15,7 +15,6 @@ describe("Windows PowerShell shell integration", () => { // Mock the helpers module mock.module("../helpers.js", { namedExports: { - execAndGetOutput: () => mockStartupFile, doesExecutableExistOnSystem: () => true, addLineToFile: (filePath, line) => { if (!fs.existsSync(filePath)) { @@ -33,6 +32,13 @@ describe("Windows PowerShell shell integration", () => { } }); + // Mock child_process execSync + mock.module("child_process", { + namedExports: { + execSync: () => mockStartupFile, + }, + }); + // Import windowsPowershell module after mocking windowsPowershell = (await import("./windowsPowershell.js")).default; }); diff --git a/src/shell-integration/supported-shells/zsh.js b/src/shell-integration/supported-shells/zsh.js index d0b179b..dbcf072 100644 --- a/src/shell-integration/supported-shells/zsh.js +++ b/src/shell-integration/supported-shells/zsh.js @@ -1,9 +1,9 @@ import { addLineToFile, doesExecutableExistOnSystem, - execAndGetOutput, removeLinesMatchingPattern, } from "../helpers.js"; +import { execSync } from "child_process"; const shellName = "Zsh"; const executableName = "zsh"; @@ -14,7 +14,7 @@ function isInstalled() { } function teardown() { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); // Removes all aliases starting with "alias npm=", "alias npx=", or "alias yarn=" // This will remove the safe-chain aliases for npm, npx, and yarn commands. @@ -24,7 +24,7 @@ function teardown() { } function setup(tools) { - const startupFile = execAndGetOutput(startupFileCommand, executableName); + const startupFile = getStartupFile(); teardown(); for (const { tool, aikidoCommand } of tools) { @@ -37,6 +37,19 @@ function setup(tools) { return true; } +function getStartupFile() { + try { + return execSync(startupFileCommand, { + encoding: "utf8", + shell: executableName, + }).trim(); + } catch (error) { + throw new Error( + `Command failed: ${startupFileCommand}. Error: ${error.message}` + ); + } +} + export default { name: shellName, isInstalled, diff --git a/src/shell-integration/supported-shells/zsh.spec.js b/src/shell-integration/supported-shells/zsh.spec.js index 37cc20f..8dabe87 100644 --- a/src/shell-integration/supported-shells/zsh.spec.js +++ b/src/shell-integration/supported-shells/zsh.spec.js @@ -15,7 +15,6 @@ describe("Zsh shell integration", () => { // Mock the helpers module mock.module("../helpers.js", { namedExports: { - execAndGetOutput: () => mockStartupFile, doesExecutableExistOnSystem: () => true, addLineToFile: (filePath, line) => { if (!fs.existsSync(filePath)) { @@ -33,6 +32,13 @@ describe("Zsh shell integration", () => { }, }); + // Mock child_process execSync + mock.module("child_process", { + namedExports: { + execSync: () => mockStartupFile, + }, + }); + // Import zsh module after mocking zsh = (await import("./zsh.js")).default; });