From d2cfbc7d6f3d3313d2887ace1f79c326ad700936 Mon Sep 17 00:00:00 2001 From: zachariah-mithani Date: Tue, 1 Sep 2026 11:26:37 -0400 Subject: [PATCH] fix(security): replace new Function template evaluation and shelljs with execFile --- index.js | 72 ++++++++++++++++++++++++-------------------------------- 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/index.js b/index.js index 166689d..c0ca025 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,9 @@ 'use strict'; const shell = require('shelljs'); +const { execFile } = require('child_process'); const path = require('path'); +const fs = require('fs'); const Executor = require('@runnerty/module-core').Executor; @@ -13,65 +15,60 @@ class decompressExecutor extends Executor { exec(res) { const COMPRESS_PROFILE = { TAR: { - SCRIPT: 'mkdir -p ${dir} && tar -C ${dir} ${verbose} -xvf "${fileName}" ', + ARGS: (dir, fileName, verbose) => ['-C', dir, ...(verbose ? [verbose] : []), '-xvf', fileName], SHELL: 'tar', EXT: 'tar' }, TAR_GZ: { - SCRIPT: 'mkdir -p ${dir} && tar -C ${dir} ${verbose} -xzvf "${fileName}" ', + ARGS: (dir, fileName, verbose) => ['-C', dir, ...(verbose ? [verbose] : []), '-xzvf', fileName], SHELL: 'tar', EXT: 'tar.gz' }, ZIP: { - SCRIPT: 'mkdir -p ${dir} && unzip -d ${dir} ${verbose} -o "${fileName}"', + ARGS: (dir, fileName, verbose) => ['-d', dir, ...(verbose ? [verbose] : []), '-o', fileName], SHELL: 'unzip', EXT: 'zip' }, BZ2: { - SCRIPT: - 'mkdir -p ${dir} && cp -fr ${fileName} ${dir}/ 2>/dev/null || true && cd ${dir} && bunzip2 ${verbose} -dk ${basename}', + ARGS: (dir, fileName, verbose) => [...(verbose ? [verbose] : []), '-dk', fileName], SHELL: 'bzip2', EXT: 'bz2' }, TAR_BZ2: { - SCRIPT: 'mkdir -p ${dir} && tar -C ${dir} ${verbose} -xjvf "${fileName}" ', + ARGS: (dir, fileName, verbose) => ['-C', dir, ...(verbose ? [verbose] : []), '-xjvf', fileName], SHELL: 'tar', EXT: 'tar.bz2' }, RAR: { - SCRIPT: - 'mkdir -p ${dir} && cp -fr ${fileName} ${dir}/ 2>/dev/null || SAME_DIR=1 && cd ${dir} && unrar ${verbose} "${basename}" -y && if [[ -z $SAME_DIR ]]; then rm -frv "${basename}"; fi;', + ARGS: (dir, fileName, verbose) => ['x', ...(verbose ? [verbose] : []), fileName, dir, '-y'], SHELL: 'unrar', EXT: 'rar' }, SEVEN_ZIP: { - SCRIPT: - 'mkdir -p ${dir} && cp -fr ${fileName} ${dir}/ 2>/dev/null || SAME_DIR=1 && cd ${dir} 1>/dev/null && 7z x "${basename}" -y && if [[ -z $SAME_DIR ]]; then rm -frv "${basename}"; fi;', + ARGS: (dir, fileName, verbose) => ['x', fileName, `-o${dir}`, '-y'], SHELL: '7z', EXT: '7z' }, TAR_TGZ: { - SCRIPT: 'mkdir -p ${dir} && tar -C ${dir} ${verbose} -xzvf "${fileName}" ', + ARGS: (dir, fileName, verbose) => ['-C', dir, ...(verbose ? [verbose] : []), '-xzvf', fileName], SHELL: 'tar', EXT: 'tar.tgz' }, TGZ: { - SCRIPT: 'mkdir -p ${dir} && tar -C ${dir} ${verbose} -xzvf "${fileName}" ', + ARGS: (dir, fileName, verbose) => ['-C', dir, ...(verbose ? [verbose] : []), '-xzvf', fileName], SHELL: 'tar', EXT: 'tgz' }, GZ: { - SCRIPT: - 'mkdir -p ${dir} && cp -fr ${fileName} ${dir}/ 2>/dev/null || true && cd ${dir} && gunzip ${verbose} -d ${basename}', + ARGS: (dir, fileName, verbose) => [...(verbose ? [verbose] : []), '-d', fileName], SHELL: 'gunzip', EXT: 'gz' } }; const fileName = res.compress_file; - const dir = res.decompress_dir; + const dir = res.decompress_dir || './'; let verbose = ''; - const basename = path.basename(fileName); const type = this.getFileType(fileName, COMPRESS_PROFILE); @@ -94,21 +91,27 @@ class decompressExecutor extends Executor { } else { if (type === 'RAR') verbose = 'x'; - const repVal = { - fileName: fileName, - dir: dir || './', - verbose: verbose, - basename: basename - }; - const script = COMPRESS_PROFILE[type].SCRIPT; - const command = this.render(script, repVal); + try { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + } catch (err) { + const endOptions = { + end: 'error', + messageLog: `Error creating target directory: ${err.message}`, + err_output: err.message + }; + return this.end(endOptions); + } + + const args = COMPRESS_PROFILE[type].ARGS(dir, fileName, verbose); - shell.exec(command, (code, stdout, stderr) => { - if (code !== 0) { + execFile(shScript, args, { cwd: dir }, (err, stdout, stderr) => { + if (err) { const endOptions = { end: 'error', - messageLog: `Error decompress process: ${stderr}`, - err_output: stderr + messageLog: `Error decompress process: ${stderr || err.message}`, + err_output: stderr || err.message }; this.end(endOptions); } else { @@ -122,19 +125,6 @@ class decompressExecutor extends Executor { } } - render(template, opts) { - return new Function( - 'return new Function (' + - Object.keys(opts).reduce((args, arg) => (args += "'" + arg + "',"), '') + - "'return `" + - template.replace(/(^|[^\\])'/g, "$1\\'") + - "`;'" + - ').apply(null, ' + - JSON.stringify(Object.keys(opts).reduce((vals, key) => vals.push(opts[key]) && vals, [])) + - ');' - )(); - } - getFileType(filename, COMPRESS_PROFILE) { const possibles = []; for (const key in COMPRESS_PROFILE) {