[grade=pending] QA sprint: commit 103 at-risk files from multi-agent sprint work

Committed by Hermes autonomous QA sprint 2026-08-13.
These files were modified during the Aug 12 sprint but never committed.
This commit is contained in:
Krystie
2026-08-12 17:34:10 -07:00
parent 0bf4406253
commit 503de258b8
105 changed files with 14057 additions and 2632 deletions
+3 -3
View File
@@ -184,10 +184,10 @@ class AuditLogger {
});
} catch (e) {
// Non-fatal — security store is a best-effort mirror
console.error('[AuditLogger] Security event emit failed:', e.message);
process.stderr.write(`[AuditLogger] Security event emit failed: ${e.message}\n`);
}
} catch (e) {
console.error('[AuditLogger] Failed to write entry:', e.message);
process.stderr.write(`[AuditLogger] Failed to write entry: ${e.message}\n`);
}
}
@@ -199,7 +199,7 @@ class AuditLogger {
}
return entries.slice(offset, offset + limit);
} catch (e) {
console.error('[AuditLogger] Failed to read:', e.message);
process.stderr.write(`[AuditLogger] Failed to read: ${e.message}\n`);
return [];
}
}
+17 -21
View File
@@ -8,6 +8,7 @@ const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const platformPaths = require('../../platform-paths');
const { log } = require('../utils/logging');
// Encryption settings
const ALGORITHM = 'aes-256-gcm';
@@ -65,7 +66,7 @@ function loadOrCreateKey() {
// Check for key in environment variable first
if (process.env.DASHCADDY_ENCRYPTION_KEY) {
encryptionKey = Buffer.from(process.env.DASHCADDY_ENCRYPTION_KEY, 'hex');
console.log('[Crypto] Using encryption key from environment variable');
log.info('crypto', 'Using encryption key from environment variable');
return encryptionKey;
}
@@ -75,16 +76,16 @@ function loadOrCreateKey() {
const keyData = fs.readFileSync(KEY_FILE, 'utf8').trim();
if (keyData.length >= 64) {
encryptionKey = Buffer.from(keyData, 'hex');
console.log('[Crypto] Loaded encryption key from file');
log.info('crypto', 'Loaded encryption key from file');
// First-run bootstrap: if .bak doesn't exist yet, write the current
// key to it. This ensures the silent recovery path is available from
// the very next restart without requiring an explicit rotateKey().
if (!fs.existsSync(KEY_FILE + '.bak')) {
try {
fs.writeFileSync(KEY_FILE + '.bak', keyData, { mode: 0o600 });
console.log(`[Crypto] Seeded ${KEY_FILE}.bak with current key for future fallback`);
log.info('crypto', 'Seeded .bak key file for future fallback');
} catch (e) {
console.warn('[Crypto] Could not seed .bak key file:', e.message);
log.warn('crypto', 'Could not seed .bak key file', { error: e.message });
}
}
// Try fallback to .bak key if primary can't decrypt existing credentials.
@@ -98,14 +99,14 @@ function loadOrCreateKey() {
encryptionKey = tryFallbackToBackupKey(Buffer.from(keyData, 'hex'), Buffer.from(backupData, 'hex'));
}
} catch (e) {
console.warn('[Crypto] Could not check backup key:', e.message);
log.warn('crypto', 'Could not check backup key', { error: e.message });
}
}
return encryptionKey;
}
// File exists but key is invalid/empty - will generate new one below
} catch (error) {
console.error('[Crypto] Error loading key file:', error.message);
log.error('crypto', error, { operation: 'loadKey' });
}
}
@@ -115,10 +116,10 @@ function loadOrCreateKey() {
try {
// Save key to file with restricted permissions
fs.writeFileSync(KEY_FILE, encryptionKey.toString('hex'), { mode: 0o600 });
console.log('[Crypto] Generated and saved new encryption key');
log.info('crypto', 'Generated and saved new encryption key');
} catch (error) {
console.warn('[Crypto] Could not save key to file:', error.message);
console.warn('[Crypto] Key will be regenerated on restart - credentials will need to be re-entered');
log.warn('crypto', 'Could not save key to file', { error: error.message });
log.warn('crypto', 'Key will be regenerated on restart - credentials will need to be re-entered');
}
return encryptionKey;
@@ -171,12 +172,7 @@ function tryFallbackToBackupKey(primaryKey, backupKey) {
if (tryDecrypt(primaryKey)) return primaryKey;
if (tryDecrypt(backupKey)) {
console.warn(
'[Crypto] Primary encryption key failed to decrypt credentials; ' +
'fell back to .encryption-key.bak. The current primary key was set ' +
'without preserving the original. Consider rotating the key explicitly ' +
'via the credential-manager API to avoid this warning next restart.'
);
log.warn('crypto', 'Primary encryption key failed to decrypt credentials; fell back to .encryption-key.bak. Consider rotating the key explicitly via the credential-manager API.');
return backupKey;
}
return primaryKey; // neither works — credential-manager.diagnose() will report 'unreadable'
@@ -291,7 +287,7 @@ function decryptFields(obj, fields = null) {
try {
result[field] = decrypt(result[field]);
} catch (error) {
console.error(`[Crypto] Failed to decrypt field '${field}':`, error.message);
log.error('crypto', error, { field, operation: 'decryptField' });
// Leave the field as-is if decryption fails
}
}
@@ -315,7 +311,7 @@ function migrateToEncrypted(credentials, sensitiveFields) {
return credentials; // Already encrypted
}
console.log('[Crypto] Migrating plaintext credentials to encrypted format');
log.info('crypto', 'Migrating plaintext credentials to encrypted format');
return encryptFields(credentials, sensitiveFields);
}
@@ -340,10 +336,10 @@ function readEncryptedFile(filePath, sensitiveFields = ['password', 'token', 'ap
}
// Plain text data - migrate it
console.log(`[Crypto] Found plaintext data in ${filePath}, will encrypt on next save`);
log.info('crypto', 'Found plaintext data', { filePath });
return parsed;
} catch (error) {
console.error(`[Crypto] Error reading ${filePath}:`, error.message);
log.error('crypto', error, { filePath, operation: 'readFile' });
return null;
}
}
@@ -357,7 +353,7 @@ function readEncryptedFile(filePath, sensitiveFields = ['password', 'token', 'ap
function writeEncryptedFile(filePath, credentials, sensitiveFields = ['password', 'token', 'apiKey', 'secret']) {
const encrypted = encryptFields(credentials, sensitiveFields);
fs.writeFileSync(filePath, JSON.stringify(encrypted, null, 2), 'utf8');
console.log(`[Crypto] Saved encrypted credentials to ${filePath}`);
log.info('crypto', 'Saved encrypted credentials', { filePath });
}
/**
@@ -377,7 +373,7 @@ function rotateKey() {
try {
fs.writeFileSync(KEY_FILE + '.bak', oldKey.toString('hex'), { mode: 0o600 });
} catch (error) {
console.warn(`[Crypto] Could not save backup key to ${KEY_FILE}.bak:`, error.message);
log.warn('crypto', 'Could not save backup key', { error: error.message });
}
try {
@@ -216,14 +216,14 @@ function csrfValidationMiddleware(req, res, next) {
// Validate both values exist
if (!cookieNonce) {
console.warn(`[CSRF] Missing CSRF cookie: ${method} ${req.path} from ${req.ip}`);
process.stderr.write(`[CSRF] Missing CSRF cookie: ${method} ${req.path} from ${req.ip}\n`);
return errorResponse(res, 403, '[DC-100] CSRF token missing', {
message: 'CSRF cookie not found. Please refresh the page (Ctrl+Shift+R) and try again.'
});
}
if (!headerToken) {
console.warn(`[CSRF] Missing CSRF header: ${method} ${req.path} from ${req.ip}`);
process.stderr.write(`[CSRF] Missing CSRF header: ${method} ${req.path} from ${req.ip}\n`);
return errorResponse(res, 403, '[DC-100] CSRF token missing', {
message: 'CSRF token not provided in request headers. Please refresh the page (Ctrl+Shift+R) and try again.'
});
@@ -247,7 +247,7 @@ function csrfValidationMiddleware(req, res, next) {
next();
} catch (err) {
console.warn(`[CSRF] Invalid CSRF token: ${method} ${req.path} from ${req.ip} - ${err.message}`);
process.stderr.write(`[CSRF] Invalid CSRF token: ${method} ${req.path} from ${req.ip} - ${err.message}\n`);
return errorResponse(res, 403, '[DC-101] CSRF token invalid', {
message: 'CSRF token validation failed. Please refresh the page (Ctrl+Shift+R) and try again.'
});
+16 -15
View File
@@ -9,6 +9,7 @@ const path = require('path');
const https = require('https');
const Docker = require('dockerode');
const platformPaths = require('../../platform-paths');
const { log } = require('../utils/logging');
const docker = new Docker();
@@ -19,7 +20,7 @@ class DockerSecurity {
constructor() {
this.config = this.loadConfig();
this.mode = VERIFICATION_MODE;
console.log(`[DockerSecurity] Initialized in ${this.mode} mode`);
log.info('security', 'Docker security initialized', { mode: this.mode });
}
/**
@@ -32,7 +33,7 @@ class DockerSecurity {
return JSON.parse(data);
}
} catch (error) {
console.warn(`[DockerSecurity] Failed to load config: ${error.message}`);
log.warn('security', 'Failed to load config', { error: error.message });
}
// Default configuration
@@ -51,7 +52,7 @@ class DockerSecurity {
try {
fs.writeFileSync(SECURITY_CONFIG_FILE, JSON.stringify(this.config, null, 2));
} catch (error) {
console.error(`[DockerSecurity] Failed to save config: ${error.message}`);
log.error('security', error, { operation: 'saveConfig' });
}
}
@@ -110,7 +111,7 @@ class DockerSecurity {
repository = repository.split(':')[0];
}
console.log(`[DockerSecurity] Fetching manifest for ${registry}/${repository}:${tag}`);
log.info('security', 'Fetching manifest', { registry, repository, tag });
return new Promise((resolve, reject) => {
const isDockerHub = registry === 'registry-1.docker.io';
@@ -216,7 +217,7 @@ class DockerSecurity {
if (this.config.updateTrustedOnPull) {
this.config.trustedDigests[imageName] = actualDigest;
this.saveConfig();
console.log(`[DockerSecurity] Added trusted digest for ${imageName}`);
log.info('security', 'Added trusted digest', { imageName });
}
}
} else if (actualDigest === trustedDigest) {
@@ -250,26 +251,26 @@ class DockerSecurity {
* @returns {Promise<object>} Verification result
*/
async verifyPulledImage(imageName) {
console.log(`[DockerSecurity] Verifying image: ${imageName}`);
log.info('security', 'Verifying image', { imageName });
try {
const actualDigest = await this.getImageDigest(imageName);
const result = await this.verifyImageDigest(imageName, actualDigest);
if (result.action === 'reject') {
console.error(`[DockerSecurity] REJECTED: ${result.reason}`);
log.error('security', 'Image REJECTED', { imageName, reason: result.reason });
throw new Error(`Image verification failed: ${result.reason}`);
} else if (result.action === 'warn') {
console.warn(`[DockerSecurity] WARNING: ${result.reason}`);
console.warn(`[DockerSecurity] Expected: ${result.trustedDigest}`);
console.warn(`[DockerSecurity] Actual: ${result.actualDigest}`);
log.warn('security', 'Image WARNING', { imageName, reason: result.reason });
log.warn('security', 'Expected digest', { imageName, digest: result.trustedDigest });
log.warn('security', 'Actual digest', { imageName, digest: result.actualDigest });
} else {
console.log(`[DockerSecurity] ACCEPTED: ${result.reason}`);
log.info('security', 'Image ACCEPTED', { imageName, reason: result.reason });
}
return result;
} catch (error) {
console.error(`[DockerSecurity] Verification error: ${error.message}`);
log.error('security', error, { imageName, operation: 'verify' });
if (this.mode === 'strict') {
throw error;
@@ -294,7 +295,7 @@ class DockerSecurity {
setTrustedDigest(imageName, digest) {
this.config.trustedDigests[imageName] = digest;
this.saveConfig();
console.log(`[DockerSecurity] Updated trusted digest for ${imageName}`);
log.info('security', 'Updated trusted digest', { imageName });
}
/**
@@ -304,7 +305,7 @@ class DockerSecurity {
removeTrustedDigest(imageName) {
delete this.config.trustedDigests[imageName];
this.saveConfig();
console.log(`[DockerSecurity] Removed trusted digest for ${imageName}`);
log.info('security', 'Removed trusted digest', { imageName });
}
/**
@@ -325,7 +326,7 @@ class DockerSecurity {
this.mode = mode;
this.config.verificationMode = mode;
this.saveConfig();
console.log(`[DockerSecurity] Verification mode set to: ${mode}`);
log.info('security', 'Verification mode set', { mode });
}
/**
+6 -5
View File
@@ -37,6 +37,7 @@
*/
const fs = require('fs');
const { log } = require('../utils/logging');
const path = require('path');
const os = require('os');
const platformPaths = require('../../platform-paths');
@@ -95,7 +96,7 @@ function createTail({ filePath, stateFile, onLine, label = 'tail', pollMs = 1000
for (const line of lines) {
if (line.trim()) {
try { onLine(line); } catch (e) {
console.error(`[${label}] onLine threw:`, e.message);
log.error('events', e, { worker: label, phase: 'onLine' });
}
}
}
@@ -106,7 +107,7 @@ function createTail({ filePath, stateFile, onLine, label = 'tail', pollMs = 1000
setTimeout(tick, pollMs);
});
stream.on('error', (e) => {
console.error(`[${label}] read error:`, e.message);
log.error('events', e, { worker: label, phase: 'read' });
setTimeout(tick, pollMs * 5);
});
});
@@ -267,11 +268,11 @@ function startFail2banWorker({ log } = {}) {
function startAll({ log } = {}) {
const workers = [];
try { workers.push(startCaddyWorker({ log })); }
catch (e) { console.error('[workers] caddy worker failed to start:', e.message); }
catch (e) { log.error('events', e, { worker: 'caddy', phase: 'start' }); }
try { workers.push(startSharedBansWorker({ log })); }
catch (e) { console.error('[workers] shared_bans worker failed to start:', e.message); }
catch (e) { log.error('events', e, { worker: 'shared_bans', phase: 'start' }); }
try { workers.push(startFail2banWorker({ log })); }
catch (e) { console.error('[workers] fail2ban worker failed to start:', e.message); }
catch (e) { log.error('events', e, { worker: 'fail2ban', phase: 'start' }); }
return {
stop() { workers.forEach(w => { try { w.stop(); } catch {} }); },
workers,
@@ -7,6 +7,7 @@
const { execSync, execFileSync } = require('child_process');
const os = require('os');
const crypto = require('crypto');
const { log } = require('../utils/logging');
const SERVICE_NAME = 'DashCaddy';
const ACCOUNT_PREFIX = 'dashcaddy';
@@ -44,7 +45,7 @@ class KeychainManager {
}
return false;
} catch {
console.warn('[Keychain] OS keychain not available, will use encrypted file storage');
log.warn('keychain', 'OS keychain not available, will use encrypted file storage');
return false;
}
}
@@ -72,7 +73,7 @@ class KeychainManager {
}
return false;
} catch (error) {
console.error(`[Keychain] Failed to store ${key}:`, error.message);
log.error('keychain', error, { key, operation: 'store' });
return false;
}
}
@@ -99,7 +100,7 @@ class KeychainManager {
}
return null;
} catch (error) {
console.error(`[Keychain] Failed to retrieve ${key}:`, error.message);
log.error('keychain', error, { key, operation: 'retrieve' });
return null;
}
}
@@ -126,7 +127,7 @@ class KeychainManager {
}
return false;
} catch (error) {
console.error(`[Keychain] Failed to delete ${key}:`, error.message);
log.error('keychain', error, { key, operation: 'delete' });
return false;
}
}
+9 -4
View File
@@ -11,6 +11,7 @@ const fs = require('fs');
const fsp = require('fs').promises;
const path = require('path');
const { DOCKER } = require('../utilities/constants');
const { log } = require('../utils/logging');
const docker = new Docker();
@@ -63,7 +64,7 @@ class LogDigest extends EventEmitter {
// Collect logs every hour
this.collectInterval = setInterval(() => {
this._collectHourlyLogs().catch(e =>
console.error('[LogDigest] Hourly collection failed:', e.message)
log.error('logdigest', e, { phase: 'hourlyCollect' })
);
}, DOCKER.DIGEST.COLLECT_INTERVAL);
@@ -71,7 +72,7 @@ class LogDigest extends EventEmitter {
this._scheduleDailyDigest();
// Run initial collection after 2 minutes
setTimeout(() => {
this._initialTimeout = setTimeout(() => {
if (this.running) {
this._collectHourlyLogs().catch(() => {});
}
@@ -89,6 +90,10 @@ class LogDigest extends EventEmitter {
clearTimeout(this.digestTimeout);
this.digestTimeout = null;
}
if (this._initialTimeout) {
clearTimeout(this._initialTimeout);
this._initialTimeout = null;
}
}
/**
@@ -195,7 +200,7 @@ class LogDigest extends EventEmitter {
hourSummary.services[appId] = serviceSummary;
}
} catch (e) {
console.error('[LogDigest] Container enumeration failed:', e.message);
log.error('logdigest', e, { phase: 'enumerateContainers' });
}
// Add to ring buffer
@@ -258,7 +263,7 @@ class LogDigest extends EventEmitter {
const delay = next.getTime() - now.getTime();
this.digestTimeout = setTimeout(() => {
this.generateDailyDigest().catch(e =>
console.error('[LogDigest] Daily digest generation failed:', e.message)
log.error('logdigest', e, { phase: 'dailyDigest' })
);
// Reschedule for tomorrow
if (this.running) this._scheduleDailyDigest();