UNPKG

@nodesecure/js-x-ray

Version:
112 lines 3.64 kB
// Import Internal Dependencies import { toLiteral } from "../estree/index.js"; import { CALL_EXPRESSION_DATA } from "../contants.js"; import { isLiteral, isTemplateLiteral } from "../estree/types.js"; import { generateWarning } from "../warnings.js"; // CONSTANTS const kUnsafeCommands = ["csrutil", "uname", "ping", "curl"]; // CONSTANTS const kIdentifierOrMemberExps = [ "child_process.spawn", "child_process.spawnSync", "child_process.exec", "child_process.execSync" ]; function isUnsafeCommand(command) { return kUnsafeCommands.some((unsafeCommand) => command.includes(unsafeCommand)); } function getCommand(commandArg) { let command = ""; switch (commandArg.type) { case "Literal": command = commandArg.value; break; case "TemplateLiteral": command = toLiteral(commandArg); break; } return command; } function concatArrayArgs(command, node) { const arrExpr = node.arguments.at(1); let finalizedCommand = command; if (arrExpr && arrExpr.type === "ArrayExpression") { arrExpr.elements .filter((element) => isLiteral(element)) .forEach((element) => { finalizedCommand += ` ${element.value}`; }); } return finalizedCommand; } /** * @description Detect spawn or exec unsafe commands * @example * child_process.spawn("csrutil", ["status"]); * * require("child_process").spawn("csrutil", ["disable"]); * * const { exec } = require("child_process"); * exec("csrutil status"); */ function validateNode(_node, ctx) { const data = ctx.context?.[CALL_EXPRESSION_DATA]; return data && kIdentifierOrMemberExps.includes(data.name) ? [ true, data.name.slice("child_process.".length) ] : [false]; } function main(node, ctx) { const { sourceFile, data: methodName, signals } = ctx; const commandArg = node.arguments[0]; if (!isLiteral(commandArg) && !isTemplateLiteral(commandArg)) { return null; } let command = getCommand(commandArg); // Aggressive mode: warn on any child_process usage if (sourceFile.sensitivity === "aggressive") { // Handle spawn/spawnSync array arguments if (methodName === "spawn" || methodName === "spawnSync") { command = concatArrayArgs(command, node); } const warning = generateWarning("unsafe-command", { value: command, location: node.loc }); sourceFile.warnings.push(warning); return signals.Skip; } // Conservative mode: existing strict validation if (isUnsafeCommand(command)) { // Spawned command arguments are filled into an Array // as second arguments. This is why we should add them // manually to the command string. if (methodName === "spawn" || methodName === "spawnSync") { command = concatArrayArgs(command, node); } const warning = generateWarning("unsafe-command", { value: command, location: node.loc }); sourceFile.warnings.push(warning); return signals.Skip; } return null; } function initialize(ctx) { kIdentifierOrMemberExps.forEach((identifierOrMemberExp) => { const moduleName = identifierOrMemberExp.split(".")[0]; ctx.sourceFile.tracer.trace(identifierOrMemberExp, { followConsecutiveAssignment: true, moduleName }); }); } export default { name: "isUnsafeCommand", validateNode, main, initialize, context: {} }; //# sourceMappingURL=isUnsafeCommand.js.map