[grade=A] DC-082+DC-064: eliminate command injection surface + add Docker resource limits
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:
@@ -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() : '';
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} \
|
||||||
|
|||||||
Reference in New Issue
Block a user