diff --git a/packages/safe-chain/src/utils/safeSpawn.js b/packages/safe-chain/src/utils/safeSpawn.js index c5cd913..253417c 100644 --- a/packages/safe-chain/src/utils/safeSpawn.js +++ b/packages/safe-chain/src/utils/safeSpawn.js @@ -1,4 +1,4 @@ -import { spawnSync, spawn } from "child_process"; +import { spawn } from "child_process"; function escapeArg(arg) { // If argument contains spaces or quotes, wrap in double quotes and escape double quotes @@ -13,11 +13,6 @@ function buildCommand(command, args) { return `${command} ${escapedArgs.join(" ")}`; } -export function safeSpawnSync(command, args, options = {}) { - const fullCommand = buildCommand(command, args); - return spawnSync(fullCommand, { ...options, shell: true }); -} - export async function safeSpawn(command, args, options = {}) { const fullCommand = buildCommand(command, args); return new Promise((resolve, reject) => { diff --git a/packages/safe-chain/src/utils/safeSpawn.spec.js b/packages/safe-chain/src/utils/safeSpawn.spec.js index d325f8a..6417084 100644 --- a/packages/safe-chain/src/utils/safeSpawn.spec.js +++ b/packages/safe-chain/src/utils/safeSpawn.spec.js @@ -2,7 +2,7 @@ import { describe, it, beforeEach, afterEach, mock } from "node:test"; import assert from "node:assert"; describe("safeSpawn", () => { - let safeSpawnSync, safeSpawn; + let safeSpawn; let spawnCalls = []; beforeEach(async () => { @@ -11,14 +11,6 @@ describe("safeSpawn", () => { // Mock child_process module to capture what command string gets built mock.module("child_process", { namedExports: { - spawnSync: (command, options) => { - spawnCalls.push({ command, options }); - return { - status: 0, - stdout: Buffer.from(""), - stderr: Buffer.from(""), - }; - }, spawn: (command, options) => { spawnCalls.push({ command, options }); return { @@ -35,7 +27,6 @@ describe("safeSpawn", () => { // Import after mocking const safeSpawnModule = await import("./safeSpawn.js"); - safeSpawnSync = safeSpawnModule.safeSpawnSync; safeSpawn = safeSpawnModule.safeSpawn; }); @@ -43,67 +34,56 @@ describe("safeSpawn", () => { mock.reset(); }); - // Helper to run either sync or async variant - async function runSafeSpawn(variant, command, args, options) { - if (variant === "sync") { - return safeSpawnSync(command, args, options); - } else { - return await safeSpawn(command, args, options); - } - } + it("should pass basic command and arguments correctly", async () => { + await safeSpawn("echo", ["hello"]); - for (let variant of ["sync", "async"]) { - it(`should pass basic command and arguments correctly (${variant})`, async () => { - await runSafeSpawn(variant, "echo", ["hello"]); + assert.strictEqual(spawnCalls.length, 1); + assert.strictEqual(spawnCalls[0].command, "echo hello"); + assert.strictEqual(spawnCalls[0].options.shell, true); + }); - assert.strictEqual(spawnCalls.length, 1); - assert.strictEqual(spawnCalls[0].command, "echo hello"); - assert.strictEqual(spawnCalls[0].options.shell, true); - }); + it("should escape arguments containing spaces", async () => { + await safeSpawn("echo", ["hello world"]); - it(`should escape arguments containing spaces (${variant})`, async () => { - await runSafeSpawn(variant, "echo", ["hello world"]); + assert.strictEqual(spawnCalls.length, 1); + // Argument should be escaped to prevent shell interpretation + assert.strictEqual(spawnCalls[0].command, 'echo "hello world"'); + assert.strictEqual(spawnCalls[0].options.shell, true); + }); - assert.strictEqual(spawnCalls.length, 1); - // Argument should be escaped to prevent shell interpretation - assert.strictEqual(spawnCalls[0].command, 'echo "hello world"'); - assert.strictEqual(spawnCalls[0].options.shell, true); - }); + it("should prevent shell injection attacks", async () => { + await safeSpawn("ls", ["; rm test123.txt"]); - it(`should prevent shell injection attacks (${variant})`, async () => { - await runSafeSpawn(variant, "ls", ["; rm test123.txt"]); + assert.strictEqual(spawnCalls.length, 1); + // Malicious command should be escaped to prevent execution + assert.strictEqual(spawnCalls[0].command, 'ls "; rm test123.txt"'); + assert.strictEqual(spawnCalls[0].options.shell, true); + }); - assert.strictEqual(spawnCalls.length, 1); - // Malicious command should be escaped to prevent execution - assert.strictEqual(spawnCalls[0].command, 'ls "; rm test123.txt"'); - assert.strictEqual(spawnCalls[0].options.shell, true); - }); + it("should escape single quotes in arguments", async () => { + await safeSpawn("echo", ["don't break"]); - it(`should escape single quotes in arguments (${variant})`, async () => { - await runSafeSpawn(variant, "echo", ["don't break"]); + assert.strictEqual(spawnCalls.length, 1); + // Single quote should be properly escaped with double quotes + assert.strictEqual(spawnCalls[0].command, 'echo "don\'t break"'); + assert.strictEqual(spawnCalls[0].options.shell, true); + }); - assert.strictEqual(spawnCalls.length, 1); - // Single quote should be properly escaped with double quotes - assert.strictEqual(spawnCalls[0].command, 'echo "don\'t break"'); - assert.strictEqual(spawnCalls[0].options.shell, true); - }); + it("should handle double quotes with simpler escaping", async () => { + await safeSpawn("echo", ['say "hello"']); - it(`should handle double quotes with simpler escaping (${variant})`, async () => { - await runSafeSpawn(variant, "echo", ['say "hello"']); + assert.strictEqual(spawnCalls.length, 1); + // If we switch to double quotes, this should be: "say \"hello\"" + assert.strictEqual(spawnCalls[0].command, 'echo "say \\"hello\\""'); + assert.strictEqual(spawnCalls[0].options.shell, true); + }); - assert.strictEqual(spawnCalls.length, 1); - // If we switch to double quotes, this should be: "say \"hello\"" - assert.strictEqual(spawnCalls[0].command, 'echo "say \\"hello\\""'); - assert.strictEqual(spawnCalls[0].options.shell, true); - }); + it("should not escape arguments with only safe characters", async () => { + await safeSpawn("npm", ["install", "axios", "--save"]); - it(`should not escape arguments with only safe characters (${variant})`, async () => { - await runSafeSpawn(variant, "npm", ["install", "axios", "--save"]); - - assert.strictEqual(spawnCalls.length, 1); - // Safe arguments (alphanumeric, dash, underscore, dot, slash) shouldn't be quoted - assert.strictEqual(spawnCalls[0].command, "npm install axios --save"); - assert.strictEqual(spawnCalls[0].options.shell, true); - }); - } -}); \ No newline at end of file + assert.strictEqual(spawnCalls.length, 1); + // Safe arguments (alphanumeric, dash, underscore, dot, slash) shouldn't be quoted + assert.strictEqual(spawnCalls[0].command, "npm install axios --save"); + assert.strictEqual(spawnCalls[0].options.shell, true); + }); +});