[grade=A] DC-082+DC-064: eliminate command injection surface + add Docker resource limits
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s

DC-082: Convert all 6 execSync() calls with template-string interpolation to
execFileSync() with argv arrays — no shell parsing of user-controlled input.
Files: routes/ca.js (5 calls), src/docker/self-updater.js (1 call).
Also removed stale execSync imports (Codex LOW finding).

DC-064: Add --memory=512m --memory-swap=1g --cpus=1.5 to docker run in start.sh
to prevent container OOM from taking down the host.

Codex grade: A (30,783 tokens). All 1539 tests pass.
This commit is contained in:
Hermes
2026-08-12 04:35:15 -07:00
parent ff81d99021
commit cdf9e8d3ef
3 changed files with 9 additions and 8 deletions
+6 -6
View File
@@ -2,7 +2,7 @@ const express = require('express');
const fs = require('fs');
const fsp = require('fs').promises;
const path = require('path');
const { execSync, execFileSync } = require('child_process');
const { execFileSync } = require('child_process');
const { exists } = require('../src/utilities/fs-helpers');
const { ValidationError } = require('../src/utilities/errors');
const { ok } = require('../src/utils/responses');
@@ -161,7 +161,7 @@ module.exports = function(ctx) {
let needsRegeneration = true;
if (await exists(certFile)) {
try {
const certDates = execSync(`openssl x509 -in "${certFile}" -noout -dates`).toString();
const certDates = execFileSync('openssl', ['x509', '-in', certFile, '-noout', '-dates']).toString();
const notAfter = certDates.match(/notAfter=(.*)/)[1].trim();
const expirationDate = new Date(notAfter);
const daysUntilExpiration = Math.floor((expirationDate - new Date()) / (1000 * 60 * 60 * 24));
@@ -172,12 +172,12 @@ module.exports = function(ctx) {
}
if (needsRegeneration) {
execSync(`openssl genrsa -out "${keyFile}" 2048`, { stdio: 'pipe' });
execFileSync('openssl', ['genrsa', '-out', keyFile, '2048'], { stdio: 'pipe' });
// Sanitize domain for safe use in shell arguments — defensive, since validation already restricts input
const safeDomain = domain.replace(/[^a-zA-Z0-9.-]/g, '_');
const subject = `/CN=${safeDomain}`;
execSync(`openssl req -new -key "${keyFile}" -out "${csrFile}" -subj "${subject}"`, { stdio: 'pipe' });
execFileSync('openssl', ['req', '-new', '-key', keyFile, '-out', csrFile, '-subj', subject], { stdio: 'pipe' });
const configContent = `[req]
distinguished_name = req_distinguished_name
@@ -200,7 +200,7 @@ ${safeDomain.includes('.') ? `DNS.2 = *.${safeDomain}` : ''}`;
await fsp.writeFile(configFile, configContent);
const serialFile = path.join(domainDir, 'ca.srl');
execSync(`openssl x509 -req -in "${csrFile}" -CA "${intermediateCert}" -CAkey "${intermediateKey}" -CAserial "${serialFile}" -CAcreateserial -out "${certFile}" -days 365 -sha256 -extfile "${configFile}" -extensions v3_req`, { stdio: 'pipe' });
execFileSync('openssl', ['x509', '-req', '-in', csrFile, '-CA', intermediateCert, '-CAkey', intermediateKey, '-CAserial', serialFile, '-CAcreateserial', '-out', certFile, '-days', '365', '-sha256', '-extfile', configFile, '-extensions', 'v3_req'], { stdio: 'pipe' });
const serverCertContent = await fsp.readFile(certFile, 'utf8');
const intermediateCertContent = await fsp.readFile(intermediateCert, 'utf8');
@@ -260,7 +260,7 @@ ${safeDomain.includes('.') ? `DNS.2 = *.${safeDomain}` : ''}`;
if (!await exists(certFile)) return null;
try {
const certInfo = execSync(`openssl x509 -in "${certFile}" -noout -subject -dates -fingerprint -sha256`).toString();
const certInfo = execFileSync('openssl', ['x509', '-in', certFile, '-noout', '-subject', '-dates', '-fingerprint', '-sha256']).toString();
const subject = certInfo.match(/subject=(.*)/) ? certInfo.match(/subject=(.*)/)[1].trim() : domain;
const notBefore = certInfo.match(/notBefore=(.*)/) ? certInfo.match(/notBefore=(.*)/)[1].trim() : '';
const notAfter = certInfo.match(/notAfter=(.*)/) ? certInfo.match(/notAfter=(.*)/)[1].trim() : '';
+2 -2
View File
@@ -16,7 +16,7 @@ const fsp = require('fs').promises;
const path = require('path');
const crypto = require('crypto');
const os = require('os');
const { execSync } = require('child_process');
const { execFileSync } = require('child_process');
const platformPaths = require('../../platform-paths');
const isWindows = platformPaths.isWindows;
@@ -714,7 +714,7 @@ class SelfUpdater extends EventEmitter {
await fsp.mkdir(destDir, { recursive: true });
// Use tar command (available on Linux, and Git Bash on Windows)
try {
execSync(`tar xzf "${tarballPath}" -C "${destDir}" --strip-components=1`, { stdio: 'pipe' });
execFileSync('tar', ['xzf', tarballPath, '-C', destDir, '--strip-components=1'], { stdio: 'pipe' });
} catch (e) {
throw new Error('Failed to extract tarball: ' + e.message);
}