2b25d81144
- Disable production source maps and hide nginx version - Reduce JSON body limit from 10gb to 50mb (uploads use multer, not JSON) - Strip database info and error details from health endpoint - Mask reCAPTCHA secret key in admin settings API responses - Whitelist sort/order query parameters in events and photos endpoints - Stop reflecting arbitrary origins in static file CORS headers - Align nginx security headers with backend Helmet CSP, remove deprecated X-XSS-Protection - Strip EXIF metadata from generated thumbnails and hero images - Bind postgres/redis dev ports to localhost in docker-compose configs - Add safeExec utility (spawn with shell:false) to prevent command injection - Convert all exec/execAsync calls in backup, restore, and database backup services to use safe spawn-based helpers
114 lines
3.2 KiB
JavaScript
114 lines
3.2 KiB
JavaScript
const { spawn } = require('child_process');
|
|
|
|
/**
|
|
* Safe command execution utilities using spawn (shell: false).
|
|
* These prevent command injection by never invoking a shell interpreter.
|
|
*/
|
|
|
|
/**
|
|
* Run a command with arguments, returning { stdout, stderr }.
|
|
* Equivalent to execAsync(cmd) but safe from injection.
|
|
*/
|
|
function spawnAsync(cmd, args = [], options = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(cmd, args, {
|
|
shell: false,
|
|
...options,
|
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
});
|
|
|
|
const stdoutChunks = [];
|
|
const stderrChunks = [];
|
|
|
|
child.stdout.on('data', chunk => stdoutChunks.push(chunk));
|
|
child.stderr.on('data', chunk => stderrChunks.push(chunk));
|
|
|
|
child.on('error', reject);
|
|
child.on('close', (code) => {
|
|
const stdout = Buffer.concat(stdoutChunks).toString();
|
|
const stderr = Buffer.concat(stderrChunks).toString();
|
|
if (code !== 0) {
|
|
const err = new Error(`${cmd} exited with code ${code}: ${stderr}`);
|
|
err.code = code;
|
|
err.stdout = stdout;
|
|
err.stderr = stderr;
|
|
return reject(err);
|
|
}
|
|
resolve({ stdout, stderr });
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Run a command and redirect stdout to a file (replaces shell `> file`).
|
|
*/
|
|
function spawnToFile(cmd, args, outputPath, options = {}) {
|
|
const fs = require('fs');
|
|
return new Promise((resolve, reject) => {
|
|
const outStream = fs.createWriteStream(outputPath);
|
|
const child = spawn(cmd, args, {
|
|
shell: false,
|
|
...options,
|
|
stdio: ['ignore', outStream, 'pipe']
|
|
});
|
|
|
|
const stderrChunks = [];
|
|
child.stderr.on('data', chunk => stderrChunks.push(chunk));
|
|
|
|
child.on('error', (err) => {
|
|
outStream.destroy();
|
|
reject(err);
|
|
});
|
|
child.on('close', (code) => {
|
|
outStream.end();
|
|
const stderr = Buffer.concat(stderrChunks).toString();
|
|
if (code !== 0) {
|
|
const err = new Error(`${cmd} exited with code ${code}: ${stderr}`);
|
|
err.code = code;
|
|
err.stderr = stderr;
|
|
return reject(err);
|
|
}
|
|
resolve({ stderr });
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Run a command and pipe a file into stdin (replaces shell `< file`).
|
|
*/
|
|
function spawnFromFile(cmd, args, inputPath, options = {}) {
|
|
const fs = require('fs');
|
|
return new Promise((resolve, reject) => {
|
|
const inStream = fs.createReadStream(inputPath);
|
|
const child = spawn(cmd, args, {
|
|
shell: false,
|
|
...options,
|
|
stdio: [inStream, 'pipe', 'pipe']
|
|
});
|
|
|
|
const stdoutChunks = [];
|
|
const stderrChunks = [];
|
|
child.stdout.on('data', chunk => stdoutChunks.push(chunk));
|
|
child.stderr.on('data', chunk => stderrChunks.push(chunk));
|
|
|
|
child.on('error', (err) => {
|
|
inStream.destroy();
|
|
reject(err);
|
|
});
|
|
child.on('close', (code) => {
|
|
const stdout = Buffer.concat(stdoutChunks).toString();
|
|
const stderr = Buffer.concat(stderrChunks).toString();
|
|
if (code !== 0) {
|
|
const err = new Error(`${cmd} exited with code ${code}: ${stderr}`);
|
|
err.code = code;
|
|
err.stdout = stdout;
|
|
err.stderr = stderr;
|
|
return reject(err);
|
|
}
|
|
resolve({ stdout, stderr });
|
|
});
|
|
});
|
|
}
|
|
|
|
module.exports = { spawnAsync, spawnToFile, spawnFromFile };
|