Fix command injection

This commit is contained in:
Sander Declerck 2025-07-18 11:15:21 +02:00
parent 8ffb0191f5
commit 41bf3252d9
No known key found for this signature in database
11 changed files with 116 additions and 29 deletions

View file

@ -1,4 +1,4 @@
import { execSync, spawnSync } from "child_process"; import { spawnSync } from "child_process";
import * as os from "os"; import * as os from "os";
import fs from "fs"; 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) { export function removeLinesMatchingPattern(filePath, pattern) {
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
return; return;

View file

@ -1,9 +1,9 @@
import { import {
addLineToFile, addLineToFile,
doesExecutableExistOnSystem, doesExecutableExistOnSystem,
execAndGetOutput,
removeLinesMatchingPattern, removeLinesMatchingPattern,
} from "../helpers.js"; } from "../helpers.js";
import { execSync } from "child_process";
const shellName = "Bash"; const shellName = "Bash";
const executableName = "bash"; const executableName = "bash";
@ -14,7 +14,7 @@ function isInstalled() {
} }
function teardown() { function teardown() {
const startupFile = execAndGetOutput(startupFileCommand, executableName); const startupFile = getStartupFile();
// Removes all aliases starting with "alias npm=", "alias npx=", or "alias yarn=" // 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. // This will remove the safe-chain aliases for npm, npx, and yarn commands.
@ -24,7 +24,7 @@ function teardown() {
} }
function setup(tools) { function setup(tools) {
const startupFile = execAndGetOutput(startupFileCommand, executableName); const startupFile = getStartupFile();
teardown(); teardown();
for (const { tool, aikidoCommand } of tools) { for (const { tool, aikidoCommand } of tools) {
@ -37,6 +37,19 @@ function setup(tools) {
return true; 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 { export default {
name: shellName, name: shellName,
isInstalled, isInstalled,

View file

@ -15,7 +15,6 @@ describe("Bash shell integration", () => {
// Mock the helpers module // Mock the helpers module
mock.module("../helpers.js", { mock.module("../helpers.js", {
namedExports: { namedExports: {
execAndGetOutput: () => mockStartupFile,
doesExecutableExistOnSystem: () => true, doesExecutableExistOnSystem: () => true,
addLineToFile: (filePath, line) => { addLineToFile: (filePath, line) => {
if (!fs.existsSync(filePath)) { 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 // Import bash module after mocking
bash = (await import("./bash.js")).default; bash = (await import("./bash.js")).default;
}); });

View file

@ -1,9 +1,9 @@
import { import {
addLineToFile, addLineToFile,
doesExecutableExistOnSystem, doesExecutableExistOnSystem,
execAndGetOutput,
removeLinesMatchingPattern, removeLinesMatchingPattern,
} from "../helpers.js"; } from "../helpers.js";
import { execSync } from "child_process";
const shellName = "Fish"; const shellName = "Fish";
const executableName = "fish"; const executableName = "fish";
@ -14,7 +14,7 @@ function isInstalled() {
} }
function teardown() { function teardown() {
const startupFile = execAndGetOutput(startupFileCommand, executableName); const startupFile = getStartupFile();
// Removes all aliases starting with "alias npm=", "alias npx=", or "alias yarn=" // 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. // This will remove the safe-chain aliases for npm, npx, and yarn commands.
@ -24,7 +24,7 @@ function teardown() {
} }
function setup(tools) { function setup(tools) {
const startupFile = execAndGetOutput(startupFileCommand, executableName); const startupFile = getStartupFile();
teardown(); teardown();
for (const { tool, aikidoCommand } of tools) { for (const { tool, aikidoCommand } of tools) {
@ -37,6 +37,19 @@ function setup(tools) {
return true; 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 { export default {
name: shellName, name: shellName,
isInstalled, isInstalled,

View file

@ -15,7 +15,6 @@ describe("Fish shell integration", () => {
// Mock the helpers module // Mock the helpers module
mock.module("../helpers.js", { mock.module("../helpers.js", {
namedExports: { namedExports: {
execAndGetOutput: () => mockStartupFile,
doesExecutableExistOnSystem: () => true, doesExecutableExistOnSystem: () => true,
addLineToFile: (filePath, line) => { addLineToFile: (filePath, line) => {
if (!fs.existsSync(filePath)) { 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 // Import fish module after mocking
fish = (await import("./fish.js")).default; fish = (await import("./fish.js")).default;
}); });

View file

@ -1,9 +1,9 @@
import { import {
addLineToFile, addLineToFile,
doesExecutableExistOnSystem, doesExecutableExistOnSystem,
execAndGetOutput,
removeLinesMatchingPattern, removeLinesMatchingPattern,
} from "../helpers.js"; } from "../helpers.js";
import { execSync } from "child_process";
const shellName = "PowerShell Core"; const shellName = "PowerShell Core";
const executableName = "pwsh"; const executableName = "pwsh";
@ -14,7 +14,7 @@ function isInstalled() {
} }
function teardown() { 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=" // 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. // This will remove the safe-chain aliases for npm, npx, and yarn commands.
@ -24,7 +24,7 @@ function teardown() {
} }
function setup(tools) { function setup(tools) {
const startupFile = execAndGetOutput(startupFileCommand, executableName); const startupFile = getStartupFile();
teardown(); teardown();
for (const { tool, aikidoCommand } of tools) { for (const { tool, aikidoCommand } of tools) {
@ -37,6 +37,19 @@ function setup(tools) {
return true; 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 { export default {
name: shellName, name: shellName,
isInstalled, isInstalled,

View file

@ -15,7 +15,6 @@ describe("PowerShell Core shell integration", () => {
// Mock the helpers module // Mock the helpers module
mock.module("../helpers.js", { mock.module("../helpers.js", {
namedExports: { namedExports: {
execAndGetOutput: () => mockStartupFile,
doesExecutableExistOnSystem: () => true, doesExecutableExistOnSystem: () => true,
addLineToFile: (filePath, line) => { addLineToFile: (filePath, line) => {
if (!fs.existsSync(filePath)) { 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 // Import powershell module after mocking
powershell = (await import("./powershell.js")).default; powershell = (await import("./powershell.js")).default;
}); });

View file

@ -1,9 +1,9 @@
import { import {
addLineToFile, addLineToFile,
doesExecutableExistOnSystem, doesExecutableExistOnSystem,
execAndGetOutput,
removeLinesMatchingPattern, removeLinesMatchingPattern,
} from "../helpers.js"; } from "../helpers.js";
import { execSync } from "child_process";
const shellName = "Windows PowerShell"; const shellName = "Windows PowerShell";
const executableName = "powershell"; const executableName = "powershell";
@ -14,7 +14,7 @@ function isInstalled() {
} }
function teardown() { 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=" // 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. // This will remove the safe-chain aliases for npm, npx, and yarn commands.
@ -24,7 +24,7 @@ function teardown() {
} }
function setup(tools) { function setup(tools) {
const startupFile = execAndGetOutput(startupFileCommand, executableName); const startupFile = getStartupFile();
teardown(); teardown();
for (const { tool, aikidoCommand } of tools) { for (const { tool, aikidoCommand } of tools) {
@ -37,6 +37,19 @@ function setup(tools) {
return true; 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 { export default {
name: shellName, name: shellName,
isInstalled, isInstalled,

View file

@ -15,7 +15,6 @@ describe("Windows PowerShell shell integration", () => {
// Mock the helpers module // Mock the helpers module
mock.module("../helpers.js", { mock.module("../helpers.js", {
namedExports: { namedExports: {
execAndGetOutput: () => mockStartupFile,
doesExecutableExistOnSystem: () => true, doesExecutableExistOnSystem: () => true,
addLineToFile: (filePath, line) => { addLineToFile: (filePath, line) => {
if (!fs.existsSync(filePath)) { 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 // Import windowsPowershell module after mocking
windowsPowershell = (await import("./windowsPowershell.js")).default; windowsPowershell = (await import("./windowsPowershell.js")).default;
}); });

View file

@ -1,9 +1,9 @@
import { import {
addLineToFile, addLineToFile,
doesExecutableExistOnSystem, doesExecutableExistOnSystem,
execAndGetOutput,
removeLinesMatchingPattern, removeLinesMatchingPattern,
} from "../helpers.js"; } from "../helpers.js";
import { execSync } from "child_process";
const shellName = "Zsh"; const shellName = "Zsh";
const executableName = "zsh"; const executableName = "zsh";
@ -14,7 +14,7 @@ function isInstalled() {
} }
function teardown() { function teardown() {
const startupFile = execAndGetOutput(startupFileCommand, executableName); const startupFile = getStartupFile();
// Removes all aliases starting with "alias npm=", "alias npx=", or "alias yarn=" // 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. // This will remove the safe-chain aliases for npm, npx, and yarn commands.
@ -24,7 +24,7 @@ function teardown() {
} }
function setup(tools) { function setup(tools) {
const startupFile = execAndGetOutput(startupFileCommand, executableName); const startupFile = getStartupFile();
teardown(); teardown();
for (const { tool, aikidoCommand } of tools) { for (const { tool, aikidoCommand } of tools) {
@ -37,6 +37,19 @@ function setup(tools) {
return true; 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 { export default {
name: shellName, name: shellName,
isInstalled, isInstalled,

View file

@ -15,7 +15,6 @@ describe("Zsh shell integration", () => {
// Mock the helpers module // Mock the helpers module
mock.module("../helpers.js", { mock.module("../helpers.js", {
namedExports: { namedExports: {
execAndGetOutput: () => mockStartupFile,
doesExecutableExistOnSystem: () => true, doesExecutableExistOnSystem: () => true,
addLineToFile: (filePath, line) => { addLineToFile: (filePath, line) => {
if (!fs.existsSync(filePath)) { 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 // Import zsh module after mocking
zsh = (await import("./zsh.js")).default; zsh = (await import("./zsh.js")).default;
}); });