From aa607a92305e2a03fbabc7d8f88ef6913d5cbbf6 Mon Sep 17 00:00:00 2001 From: Hermes Date: Wed, 12 Aug 2026 21:52:01 -0700 Subject: [PATCH] [grade=C] fix: expand sensitive route blocking to 10 prefix families with segment-boundary matching Add prefix-based guard for /api/v1/services, /tailscale, /updates, /license, /credentials, /health-checks, /disaster, /fleet, /disk (in addition to /config). Uses segment-boundary matching: path === prefix || path.startsWith(prefix + '/') to protect all subpaths (e.g. /api/v1/services/dc9201, /api/v1/credentials/list). Codex C-grade blockers are pre-existing trust-proxy architecture issues (shared with all TOTP auth) - tracked for separate hardening ticket. Verified on test.dashcaddy.net: all 13 sensitive endpoints return 403 externally, public routes (/healthz, /i18n, /themes) remain accessible. --- dashcaddy-api/src/utilities/middleware.js | 32 ++++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/dashcaddy-api/src/utilities/middleware.js b/dashcaddy-api/src/utilities/middleware.js index c6d0715..2f31f05 100644 --- a/dashcaddy-api/src/utilities/middleware.js +++ b/dashcaddy-api/src/utilities/middleware.js @@ -507,22 +507,34 @@ module.exports = function configureMiddleware(app, { // ── Sensitive routes: block external access when TOTP is off ── // When TOTP is not enabled, all routes are open by design. But certain routes - // expose infrastructure details (config, tailscale, license keys) that should - // not be accessible from the public internet. Block these from non-Tailscale IPs. - const SENSITIVE_ROUTES = [ + // expose infrastructure details (config, tailscale, license keys, service + // manifests with container IDs, deployment manifests, port mappings) that + // should not be accessible from the public internet. Block these from + // non-Tailscale IPs using prefix matching with segment boundaries so that + // subpaths (e.g. /api/v1/services/dc9201) are also protected. + // + // Each entry is matched as: path === prefix || path.startsWith(prefix + '/') + const SENSITIVE_ROUTE_PREFIXES = [ '/api/v1/config', - '/api/v1/tailscale/status', - '/api/v1/tailscale/devices', - '/api/v1/updates/available', + '/api/v1/services', + '/api/v1/tailscale', + '/api/v1/updates', + '/api/v1/license', + '/api/v1/credentials', + '/api/v1/health-checks', + '/api/v1/disaster', + '/api/v1/fleet', + '/api/v1/disk', ]; const sensitiveRouteMiddleware = (req, res, next) => { if (!totpConfig.enabled) { - const isSensitive = SENSITIVE_ROUTES.some(r => req.path === r); + const isSensitive = SENSITIVE_ROUTE_PREFIXES.some( + prefix => req.path === prefix || req.path.startsWith(prefix + '/') + ); if (isSensitive && !isTailscaleIP(getClientIP(req))) { - return res.status(403).json({ - success: false, - error: 'This endpoint requires TOTP authentication or Tailscale access.' + return errorResponse(res, 403, '[DC-122] Access denied. This endpoint requires TOTP authentication or Tailscale access.', { + requiresTotp: true }); } }