DC-084/085/089/090: Quick wins batch
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s

DC-084: Add .dockerignore (excludes __tests__/, .git/, node_modules/, coverage/)
DC-085: Replace Math.random() with crypto.randomUUID()/crypto.randomBytes() for IDs
DC-089: Add dedicated rate limiter on POST /license/activate (10 attempts/15min)
DC-090: Pin Node.js to 20.11.1-alpine3.19 + add engines field to package.json

All 1539 tests pass. ESLint: 0 errors.
This commit is contained in:
Hermes
2026-08-12 02:24:28 -07:00
parent bb20f02cbf
commit 5c02bfba1d
6 changed files with 33 additions and 11 deletions
+11 -7
View File
@@ -1,10 +1,14 @@
node_modules/
__tests__/ __tests__/
jest.config.js .git/
.env
.encryption-key
.gitignore .gitignore
.dockerignore node_modules/
*.log coverage/
*.md *.md
docker-compose.yml .eslintrc.js
jest.config.js
npm-debug.log*
.env*
.env.example
.DS_Store
*.log
dc.png
+1 -1
View File
@@ -1,4 +1,4 @@
FROM node:20-alpine FROM node:20.11.1-alpine3.19
WORKDIR /app WORKDIR /app
+3
View File
@@ -3,6 +3,9 @@
"version": "1.15.0", "version": "1.15.0",
"description": "DashCaddy API server - Dashboard backend for Docker, Caddy & DNS management", "description": "DashCaddy API server - Dashboard backend for Docker, Caddy & DNS management",
"main": "server.js", "main": "server.js",
"engines": {
"node": ">=20.0.0"
},
"scripts": { "scripts": {
"start": "node server.js", "start": "node server.js",
"test": "jest", "test": "jest",
+14 -1
View File
@@ -1,7 +1,20 @@
const express = require('express'); const express = require('express');
const rateLimit = require('express-rate-limit');
const { success, error: errorResponse } = require('../src/utils/responses'); const { success, error: errorResponse } = require('../src/utils/responses');
const { ValidationError } = require('../src/utilities/errors'); const { ValidationError } = require('../src/utilities/errors');
// Dedicated rate limiter for license activation — prevents brute-force key guessing.
// Pro keys follow a predictable format (DC-XXX-XXXXX-XXXXXX), so without rate
// limiting an attacker could enumerate valid keys.
const licenseActivateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // 10 attempts per window per IP
standardHeaders: true,
legacyHeaders: false,
message: { success: false, error: 'Too many license activation attempts. Please try again later.' },
skip: () => process.env.NODE_ENV === 'test',
});
/** /**
* License routes factory * License routes factory
* @param {Object} deps - Explicit dependencies * @param {Object} deps - Explicit dependencies
@@ -13,7 +26,7 @@ module.exports = function({ licenseManager, asyncHandler }) {
const router = express.Router(); const router = express.Router();
// Activate a license code // Activate a license code
router.post('/activate', asyncHandler(async (req, res) => { router.post('/activate', licenseActivateLimiter, asyncHandler(async (req, res) => {
const { code } = req.body; const { code } = req.body;
if (!code) { if (!code) {
throw new ValidationError('License code is required'); throw new ValidationError('License code is required');
@@ -10,6 +10,7 @@
const { execFile } = require('child_process'); const { execFile } = require('child_process');
const { promisify } = require('util'); const { promisify } = require('util');
const crypto = require('crypto');
const dns = require('dns'); const dns = require('dns');
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
@@ -117,7 +118,7 @@ class RFC2136Provider extends BaseDNSProvider {
*/ */
async _runNsupdate(commands) { async _runNsupdate(commands) {
const script = commands.join('\n') + '\n'; const script = commands.join('\n') + '\n';
const tmpFile = path.join(os.tmpdir(), `nsupdate-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.cmd`); const tmpFile = path.join(os.tmpdir(), `nsupdate-${crypto.randomBytes(4).toString('hex')}.cmd`);
try { try {
await fs.promises.writeFile(tmpFile, script, { mode: 0o600 }); await fs.promises.writeFile(tmpFile, script, { mode: 0o600 });
@@ -6,6 +6,7 @@
const https = require('https'); const https = require('https');
const http = require('http'); const http = require('http');
const crypto = require('crypto');
const EventEmitter = require('events'); const EventEmitter = require('events');
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
@@ -349,7 +350,7 @@ class HealthChecker extends EventEmitter {
// Create new incident // Create new incident
const incident = { const incident = {
id: `incident-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, id: `incident-${crypto.randomUUID()}`,
serviceId, serviceId,
type, type,
message, message,