[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 fs = require('fs');
const fsp = require('fs').promises; const fsp = require('fs').promises;
const path = require('path'); const path = require('path');
const { execSync, execFileSync } = require('child_process'); const { execFileSync } = require('child_process');
const { exists } = require('../src/utilities/fs-helpers'); const { exists } = require('../src/utilities/fs-helpers');
const { ValidationError } = require('../src/utilities/errors'); const { ValidationError } = require('../src/utilities/errors');
const { ok } = require('../src/utils/responses'); const { ok } = require('../src/utils/responses');
@@ -161,7 +161,7 @@ module.exports = function(ctx) {
let needsRegeneration = true; let needsRegeneration = true;
if (await exists(certFile)) { if (await exists(certFile)) {
try { 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 notAfter = certDates.match(/notAfter=(.*)/)[1].trim();
const expirationDate = new Date(notAfter); const expirationDate = new Date(notAfter);
const daysUntilExpiration = Math.floor((expirationDate - new Date()) / (1000 * 60 * 60 * 24)); const daysUntilExpiration = Math.floor((expirationDate - new Date()) / (1000 * 60 * 60 * 24));
@@ -172,12 +172,12 @@ module.exports = function(ctx) {
} }
if (needsRegeneration) { 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 // Sanitize domain for safe use in shell arguments — defensive, since validation already restricts input
const safeDomain = domain.replace(/[^a-zA-Z0-9.-]/g, '_'); const safeDomain = domain.replace(/[^a-zA-Z0-9.-]/g, '_');
const subject = `/CN=${safeDomain}`; 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] const configContent = `[req]
distinguished_name = req_distinguished_name distinguished_name = req_distinguished_name
@@ -200,7 +200,7 @@ ${safeDomain.includes('.') ? `DNS.2 = *.${safeDomain}` : ''}`;
await fsp.writeFile(configFile, configContent); await fsp.writeFile(configFile, configContent);
const serialFile = path.join(domainDir, 'ca.srl'); 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 serverCertContent = await fsp.readFile(certFile, 'utf8');
const intermediateCertContent = await fsp.readFile(intermediateCert, 'utf8'); const intermediateCertContent = await fsp.readFile(intermediateCert, 'utf8');
@@ -260,7 +260,7 @@ ${safeDomain.includes('.') ? `DNS.2 = *.${safeDomain}` : ''}`;
if (!await exists(certFile)) return null; if (!await exists(certFile)) return null;
try { 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 subject = certInfo.match(/subject=(.*)/) ? certInfo.match(/subject=(.*)/)[1].trim() : domain;
const notBefore = certInfo.match(/notBefore=(.*)/) ? certInfo.match(/notBefore=(.*)/)[1].trim() : ''; const notBefore = certInfo.match(/notBefore=(.*)/) ? certInfo.match(/notBefore=(.*)/)[1].trim() : '';
const notAfter = certInfo.match(/notAfter=(.*)/) ? certInfo.match(/notAfter=(.*)/)[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 path = require('path');
const crypto = require('crypto'); const crypto = require('crypto');
const os = require('os'); const os = require('os');
const { execSync } = require('child_process'); const { execFileSync } = require('child_process');
const platformPaths = require('../../platform-paths'); const platformPaths = require('../../platform-paths');
const isWindows = platformPaths.isWindows; const isWindows = platformPaths.isWindows;
@@ -714,7 +714,7 @@ class SelfUpdater extends EventEmitter {
await fsp.mkdir(destDir, { recursive: true }); await fsp.mkdir(destDir, { recursive: true });
// Use tar command (available on Linux, and Git Bash on Windows) // Use tar command (available on Linux, and Git Bash on Windows)
try { 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) { } catch (e) {
throw new Error('Failed to extract tarball: ' + e.message); throw new Error('Failed to extract tarball: ' + e.message);
} }
+1
View File
@@ -136,6 +136,7 @@ else
fi fi
docker run -d --restart unless-stopped --name ${CONTAINER_NAME} \ docker run -d --restart unless-stopped --name ${CONTAINER_NAME} \
--memory=512m --memory-swap=1g --cpus=1.5 \
--add-host=get.dashcaddy.net:194.233.88.206 \ --add-host=get.dashcaddy.net:194.233.88.206 \
--add-host=get2.dashcaddy.net:194.233.88.206 \ --add-host=get2.dashcaddy.net:194.233.88.206 \
--dns ${DNS_PRIMARY} \ --dns ${DNS_PRIMARY} \