mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
115 lines
3.7 KiB
JavaScript
115 lines
3.7 KiB
JavaScript
import { describe, it, beforeEach, afterEach, mock } from "node:test";
|
|
import assert from "node:assert";
|
|
|
|
describe("safeSpawn", () => {
|
|
let safeSpawn;
|
|
let spawnCalls = [];
|
|
|
|
beforeEach(async () => {
|
|
spawnCalls = [];
|
|
|
|
// Mock child_process module to capture what command string gets built
|
|
mock.module("child_process", {
|
|
namedExports: {
|
|
spawn: (command, options) => {
|
|
spawnCalls.push({ command, options });
|
|
return {
|
|
on: (event, callback) => {
|
|
if (event === "close") {
|
|
// Simulate immediate success
|
|
setTimeout(() => callback(0), 0);
|
|
}
|
|
},
|
|
};
|
|
},
|
|
execSync: (cmd, opts) => {
|
|
// Simulate 'command -v' returning full path
|
|
const match = cmd.match(/command -v (.+)/);
|
|
if (match) {
|
|
return `/usr/bin/${match[1]}\n`;
|
|
}
|
|
return "";
|
|
},
|
|
},
|
|
});
|
|
|
|
mock.module("os", {
|
|
namedExports: {
|
|
platform: () => "win32",
|
|
},
|
|
});
|
|
|
|
// Import after mocking
|
|
const safeSpawnModule = await import("./safeSpawn.js");
|
|
safeSpawn = safeSpawnModule.safeSpawn;
|
|
});
|
|
|
|
afterEach(() => {
|
|
mock.reset();
|
|
});
|
|
|
|
it("should pass basic command and arguments correctly", async () => {
|
|
await safeSpawn("echo", ["hello"]);
|
|
|
|
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"]);
|
|
|
|
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"]);
|
|
|
|
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"]);
|
|
|
|
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"']);
|
|
|
|
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"]);
|
|
|
|
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);
|
|
});
|
|
|
|
it(`should escape ampersand character`, async () => {
|
|
await safeSpawn("npx", ["cypress", "run", "--env", "password=foo&bar"]);
|
|
|
|
assert.strictEqual(spawnCalls.length, 1);
|
|
// & should be escaped by wrapping the arg in quotes
|
|
assert.strictEqual(
|
|
spawnCalls[0].command,
|
|
'npx cypress run --env "password=foo&bar"'
|
|
);
|
|
assert.strictEqual(spawnCalls[0].options.shell, true);
|
|
});
|
|
});
|