fix(config): monitoring.public gate was dead 4 ways — live gate + schema + dedupe (DC-096) [glm-grade=A]
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s

The documented hardening option for exposed deploys (monitoring: {public: false}
in config.json / MONITORING_PUBLIC env) never worked:
1. applyConfigFields dropped the monitoring key entirely
2. monitoring missing from config-schema KNOWN_KEYS (Unknown-key warnings)
3. MONITORING_PUBLIC frozen at mount + re-required singleton instead of injected dep
4. PUBLIC_ROUTES had unconditional duplicate entries defeating the gated spread

- site.js: copy monitoring through; drop dead write-only siteConfig.caName
- config-schema: +monitoring key, validateMonitoring (public must be boolean);
  remove never-written typo-footgun keys setupCompleted/setupMode (git -S: zero writers ever)
- middleware: live isMonitoringPublic() (env > config > default public), per-request
  gate via monitoring:true flag, remove duplicate unconditional route entries
- default unchanged (endpoints stay public — System Overview widget)

Tests: +11 (__tests__/monitoring-public-gate-dc096.test.js); suite 118/2735 green.
Judge: GLM-5.3 cold-read A (deleg_98aba845), URN urn:ump:zgvtskqljurasdagc632atnybb2p6gakk4cxcmjd3i4ivy7rvwta
This commit is contained in:
Hermes
2026-08-22 21:35:51 -07:00
parent 83ef84d218
commit bb59595d6d
4 changed files with 247 additions and 25 deletions
+25 -2
View File
@@ -10,7 +10,7 @@ const VALID_DNS_PROVIDERS = ['technitium', 'cloudflare', 'rfc2136', 'manual'];
const KNOWN_KEYS = [
'tld', 'caName', 'dns', 'dnsServers', 'dashboardHost', 'timezone', 'theme',
'updatedAt', 'timestamp', 'logo', 'logoPosition', 'favicon', 'weather',
'setupComplete', 'setupCompleted', 'setupMode', 'onboardingCompleted',
'setupComplete', 'onboardingCompleted',
'configurationType', 'defaults', 'customLogo', 'customFavicon',
'dashboardTitle', 'tailscale', 'license', 'skipped',
'routingMode', 'domain', 'email', 'defaultIP', 'pylon',
@@ -18,7 +18,13 @@ const KNOWN_KEYS = [
// license-manager.js persists the last activation to config.licenseBackup
// (restore-on-restart path); src/config/migrations.js stamps _version.
// Both are first-party writes — see DC-091.
'licenseBackup', '_version'
'licenseBackup', '_version',
// DC-096: monitoring.public gates whether /api/v1/monitoring/stats and
// /api/v1/health-checks/status are public (middleware.js isMonitoringPublic).
// Removed 'setupCompleted' and 'setupMode' — never written by any code
// (past or present); they only existed here, where they masked the actual
// typo of the real key `setupComplete` (writers: setup-wizard.js).
'monitoring'
];
/**
@@ -162,6 +168,22 @@ function validateKnownKeys(ctx, config) {
}
}
/**
* @param {{errors:string[], warnings:string[]}} ctx
* @param {object} config
*/
function validateMonitoring(ctx, config) {
if (config.monitoring === undefined) return;
if (typeof config.monitoring !== 'object' || config.monitoring === null) {
ctx.errors.push('monitoring must be an object');
return;
}
if (config.monitoring.public !== undefined
&& typeof config.monitoring.public !== 'boolean') {
ctx.errors.push('monitoring.public must be a boolean');
}
}
/**
* Validate a config object and return errors/warnings.
* @param {object} config - The config object to validate
@@ -183,6 +205,7 @@ function validateConfig(config) {
validateTheme(ctx, config);
validateRoutingMode(ctx, config);
validateDomain(ctx, config);
validateMonitoring(ctx, config);
validateKnownKeys(ctx, config);
return { valid: errors.length === 0, errors, warnings };