Audit P0-5 + deploy_app truth (batch 5 of the 2026-10-27 sweep, AUDIT-2026-10-27.md committed here): ROUTE ADDITIONS: - routes/containers.js GET / — container list (dockerode listContainers); the router had NO list route, so dashcaddy_list_containers 404'd always - routes/dns.js GET /records — record list via active provider with a list-records capability guard (501 + guidance for push-only providers like RFC2136); dashcaddy_list_dns called GET /dns, never existed HANDLER FIXES (src/mcp/mcp-core.js): - get_service resolves from GET /services by id/name (single-service GET never existed) with a known-ids 404 - check_health + diagnose -> /health/service/:id and /health/services (both previously hit nonexistent routes) - container_action remove -> DELETE /containers/:id (was /:id/remove) - system_health/system_metrics via apiCall error envelope (metrics text capped at 64KB) - search_catalog applies category filter client-side (/catalog/search has no category param — constraint was silently dropped) - generate_caddyfile defaults authService=status when auth:true (auth flag alone silently produced UNGATED Caddyfiles) - deploy_app honest re-contract: registers service + generates Caddyfile, does NOT claim image pull/container creation/deployed:true; base domain read from /config (hardcoded owner .sami TLD removed); caddy-code failure now surfaces as caddyfileError + FAILED message - list_fleet description gains Premium hint VERIFIER (audit #40): EXPECTED table 12 -> 34 rows covering EVERY tool's (method,path); NEW Layer-3 handler-derived test drives the real createToolHandler for all 28 tools and strictly resolves every captured call mount-aware (a vacuous first draft of this gate was caught and fixed pre-grade); extractRoutes regex extended to apiRouter.get inline decls; Bearer credential-passthrough test added (DC-162 coverage gap) NEW TESTS: dns-records.routes.test.js (5 route-level cases incl. 501 capability behavior); deploy_app partial-failure + no-.sami tests HOST RELIABILITY (same sweep, evidenced here): jest maxWorkers 50% — default 5-worker runs repeatedly OOM-killed this host's gateway (dmesg 2026-10-29 00:58:40; swap was 4G/4G full). Gateway protected separately via systemd OOMScoreAdjust=-500 drop-in + /swapfile2 (host ops, graded B urn:ump:35dpnggtxsqrxvlqm2d2buqkbnggw3acflbccnkqpqyiiijcxx3q). DC-171 DNS provider-parity ticket (Sami, plug-in-directly) appended to backlog. Full suite: 173/173 suites, 3740/3740 tests. Judge B r1 (urn:ump:jsobkg2t2omzvpinel2fufk7sspoy3vubsrth3e6lat3ygolvffq) blockers all fixed: dns.js 501 arg order (the file aliases error() message-first — the sweep's 'reversed-arg' finding was a false positive for its 7 sites; lesson #14), deploy_app partial-failure honesty, handler-derived verifier.
13 KiB
DashCaddy Full Sweep — 2026-10-27
Branch: dc/DC-168-calendar-widget @ 6eaee81 · Suite: 3704/3705 passed (1 failure = date-expired fixture, fixed this sweep) · Lint: 12 errors (11 test-only), syntax clean.
46 unique findings: 8 high · 16 medium · 18 low. Method: 3 parallel audit agents (backend, frontend, product consistency) + jest + eslint; the top auth finding was verified live (sandbox boot + real minted API key). Cross-agent duplicates merged.
P0 — Broken functionality (verified)
1. TOTP gate blocks ALL JWT/API-key auth when TOTP is enabled — HIGH (live-verified)
src/utilities/middleware.js:531-598 — totpAuthMiddleware registers before jwtApiKeyAuthMiddleware and only checks session cookies; the JWT/API-key verifier never runs. Agent booted the real app with TOTP enabled, minted a valid dk_ key, and GET /api/v1/mcp/info returned 401 with the TOTP gate's message. MCP-over-HTTP ("Connect your AI"), fleet endpoints, and every machine-credential integration are dead on any TOTP-protected install (i.e., production).
Fix: in the TOTP gate, attempt verifyJWT/verifyAPIKey before rejecting (or register the JWT/API-key gate first and let the TOTP gate pass through when req.auth is set). Tests missed it because mcp.routes.test.js injects req.auth directly.
2. Calendar widget writes all fail with 403 CSRF — HIGH
status/js/calendar-widget.js:269-390 — all four mutations (discover / save / toggle / delete) use raw fetch with no X-CSRF-Token; backend csrf-protection.js rejects non-GETs with [DC-100]. Test Connection, Save, toggle, remove → all 403. The DC-168 feature we just shipped is write-broken.
Fix: route the calls through the existing secureFetch helper (injects token + auto-retries DC-100/101).
3. CSP silently kills inline onclick handlers — HIGH
status/index.html:223,1051 — the meta CSP allows no unsafe-inline/unsafe-hashes, so onclick= attributes are blocked: Disk Safety button, AI-chat FAB, and the injected Disk modal's Save/Clean-Up buttons do nothing in all modern browsers.
Fix: wire addEventListener in disk-settings.js / ai-chat.js init; drop the inline attributes.
4. Workflows UI broken — double-prefix route bug still live; the fix exists on an unmerged branch — HIGH
routes/workflows.js:37-78 declares /workflows/... while mounted at /workflows → /api/v1/workflows/workflows/.... Dashboard calls single-prefix → every workflow toggle and Run Now 404s. Premium-gated feature broken for paying customers. openapi.yaml documents the doubled paths (doc faithfully describing the bug).
Root cause: commit e524d16 (DC-166: workflows fix + MCP 17→28 tools) lives only on dc/DC-166-mcp-completion — never merged into this branch (verified via merge-base).
Fix: merge DC-166 into this branch, resolve mcp-core.js conflicts, re-run MCP suites.
5. Six MCP tools map to nonexistent routes — 404 at call time — HIGH
src/mcp/mcp-core.js:268-300 — get_service (→GET /services/:serviceId ✗), check_health both modes (→/services/:id/health ✗, /health/all ✗), list_containers (→GET /containers ✗ no list route), container_action remove (→DELETE /containers/:id/remove ✗), list_dns (→GET /dns ✗). tools/list still advertises all of them to AI clients.
Fix: repoint to real routes (add GET /containers list route), and extend the route-existence test to EVERY tool (currently only the 11 DC-166 tools are covered — that's how this survived).
6. Calendar error path calls a nonexistent method — HIGH
status/js/calendar-widget.js:329 — catch block calls errorHandler.log() which doesn't exist (window.ErrorHandler only has logError, and args are reversed anyway). The "Calendar offline" chip state is unreachable; every failed poll throws an unhandled rejection instead.
Fix: errorHandler.logError('calendar-widget', e).
7. Calendar modal unusable on mobile — HIGH
status/js/calendar-widget.js:18 — inline min-width:560px on the modal beats the ≤700px media query (inline styles win), so it overflows horizontally on phones. Codebase already needed !important for this exact pattern elsewhere.
Fix: size via a CSS class, drop the inline min-width.
8. dashcaddy_deploy_app MCP tool lies about deploying — MEDIUM (borderline high)
src/mcp/mcp-core.js:318-358 — returns deployed:true + "Access it at https://plex.sami" but never pulls an image, creates a container, or writes a Caddyfile — only generates text and adds a dashboard entry; its own nextSteps tell the user to run docker manually. Also hardcodes the owner's private .sami TLD into what would be customer deployments (same hardcode in app-selector.js:42, dns-template-selector.js:37,161).
Fix: route through the real POST /api/v1/apps/deploy, or re-contract as a "plan deployment" helper; read the base domain from site config.
P1 — Reliability & security
- MEDIUM — DNS check-update/update handlers hang.
routes/dns.js:669-800: barereturns, empty else/catch branches → request never answered until client timeout; non-JSON upstream dies inJSON.parseinside the swallowing catch. Fix: every path responds or throws typed. - MEDIUM — Seven reversed-arg
errorResponse(res, '<msg>', <code>)callsites (dns.js:624,735,817,errorlogs.js:219,ssl-monitor.js:95,99,disk-space.js:133) hit the DC-062 guard → reachable paths 500 instead of intended 4xx/50x. This is the exact class DC-063 claimed fixed. Fix: reorder + grep gate to prevent regressions. - MEDIUM — Calendar ICS fetch timeout silently ignored —
routes/calendar.js:177passesopts.timeout, whichsrc/utils/http.jsexplicitly strips (undici); effective deadline 5s not 15s. Fix:fetchT(url, {}, 15000). - MEDIUM — StateManager writes are non-atomic.
state-manager.js:85,138truncate-then-write; crash mid-write corrupts services.json/config.json with no recovery (read only special-cases ENOENT). share-store already does tmp+rename correctly — reuse it. - MEDIUM — Tailscale tailnet gate fails open on daemon errors (
middleware.js:139-184) — a tailscaled outage silently disables the IP allowlist while config claims enforcement. Fix: fail closed or make it loud. - MEDIUM — WS auth fallback fails open.
dashboard-ws.js:75-86defaults to "any non-empty session cookie" when the real verifier isn't injected — the exact DC-061 footgun. Fix: default deny + loud log. - MEDIUM — MCP
generate_caddyfileauth flag is a silent no-op (mcp-core.js:375-383) — "Enable SSO auth gate" produces a Caddyfile with NO auth gate, no error. Security-relevant for AI-generated configs. - MEDIUM — SW offline fallback is dead code (
status/sw.js:62-64):caches.match(...) || caches.match('/index.html')— a Promise is always truthy, fallback never runs; plus 5xx responses get cached and later served as the app shell (noresponse.okchecks); plus install is all-or-nothingaddAllof 36 URLs — one miss keeps the stale SW in control indefinitely. - LOW — Host ingest keys HMAC'd with hardcoded fallback pepper (
host-registry.js:97-103) when the pepper file is missing — and no generation/persistence path exists at all. - LOW — Calendar source update wipes the other credential type (updating pass nulls token and vice versa)
routes/calendar.js:144-148. - LOW — Authenticated SSRF surface: calendar sources + /discover accept localhost http + arbitrary https, status differentials allow internal port-scanning; no link-local/private-CIDR guard (169.254.169.254 over https).
- LOW — Public GET /api/v1/config returns raw config.json verbatim and 500s if corrupt (
routes/config/settings.js:26-33, route is in PUBLIC_ROUTES) — pairs badly with finding 12. - LOW — MCP search_catalog drops the category filter when a query is present (
mcp-core.js:304-311). - LOW — system_health/system_metrics tools bypass apiCall — no timeout/size-cap/error envelope, unlike every other tool.
- LOW — Fleet tool 403s for free licenses with no premium hint in tools/list.
P2 — Frontend polish & races
- MEDIUM — In-flight guard silently drops forced refreshes (
calendar-widget.js:284) — save/toggle followed by a mid-flight poll = stale chip for up to 5 min. - MEDIUM — openModal issues two overlapping fetch sequences (no in-flight guard; last-response-wins can repaint with stale data).
- MEDIUM — Source toggle/delete handlers: unguarded fetch, no resp.ok check, optimistic re-render — failures re-render as success.
- MEDIUM —
--cal-dottheme overrides are dead CSS — variables scoped to elements that are never ancestors of.cal-dot(dashboard.css:4316,4347-4358); the WCAG-tuned DC-168 dot colors never render. - MEDIUM — Calendar chip keyboard-inaccessible; modal lacks Escape/click-outside/focus management (
role=buttonwith no keydown;displayset directly, bypassing wireModal). - LOW — Calendar GETs bypass secureFetch — no 15s timeout; one hung request wedges
refresh()forever (pairs with 24). - LOW — Polling ignores
document.hidden— 5-min calendar polls + 1s health polls run 24/7 in background tabs. - LOW — log-insights.js still uses blocking
alert()6× despite the toast convention. - LOW — Windows path
C:\caddy\Caddyfileprefilled on a Linux deployment (globals.js:39). - LOW — One malformed event start (null/invalid) crashes the cache merge and flips the whole widget offline instead of skipping the event (
calendar-cache.js:8).
P3 — Professional/docs (the "polish" tier)
- HIGH (credibility) — DC-166 branch never merged (see P0-4): capabilities manifest and changelog advertise a toolset this branch doesn't ship.
- MEDIUM — Changelog malformed: two
[Unreleased]sections;[1.17.0]before older entries;[1.5.0]after[1.13.4]; no[1.16.0]though package.json is 1.17.0; internal judge grades + UMP URNs sitting in customer-facing copy; DC-166 and DC-169 have no entries at all. - MEDIUM — Three-way version drift: VERSION file 1.15.0, package.json 1.17.0, openapi.yaml 1.15.0; start.sh stamps builds from the stale VERSION.
- MEDIUM — README quickstart endpoints omit
/v1— every example 404s as written (README.md:283-300). - LOW — README says "50+ templates"; the measured catalog is 78 (DC-165 fixed marketing but not the repo README).
- LOW — API-SURFACE.md stale (2026-07-13, 285 vs 290 routes) and documents the broken workflows paths as canonical; wrong PUBLIC_ROUTES line refs.
- MEDIUM — MCP mapping verifier covers only the 11 new tools — extend the EXPECTED table to every tool and fail on any unresolved (method,path): turns tool↔route drift into a CI gate.
- LOW — Test-suite time bomb (fixed this sweep):
calendar.routes.test.jshardcodedDTSTART:20261007which silently expired out of the default window Oct 14 — fixture now builds tomorrow's date at runtime. Worth a repo-wide grep for hardcoded future dates in fixtures.
Also this sweep
- Fixed + verified: calendar test fixture (15/15 green) —
__tests__/routes/calendar.routes.test.js, uncommitted. - Uncommitted in tree (pre-existing, not mine):
status/dist/features.js,status/sw.js. - Lint: 12 errors = 10×
no-eval+ 1×no-new-funcin test files, 0 production errors remaining (credentials.js control-regex self-resolved in the response-helper refactor). 1027 warnings mostly unused-vars noise — awarnings → 0pass would be a cheap professionalism win.
Recommended execution order
- Merge
dc/DC-166-mcp-completioninto this branch (fixes P0-4, shrinks P0-5, restores toolset truth) → re-run MCP suites. - Calendar widget fix batch: secureFetch (CSRF+timeout),
errorHandler.logError, modal CSS class, race/coalesce fixes, ARIA/Escape (P0-2, P0-6, P0-7, 24-30). - TOTP/JWT gate ordering (P0-1) + a chain-level integration test that exercises the real middleware chain, not injected
req.auth. - CSP event-handler wiring (P0-3).
- MCP truth batch: repoint dead tools, deploy_app re-contract + TLD config, auth-flag fix, full route-existence gate (P0-5, P0-8, 15, 21-23, 40).
- Reliability batch: DNS hang, errorResponse arg order + grep gate, ICS timeout, atomic state writes, WS/tailscale fail-closed (9-14, 20).
- Docs/polish batch: changelog rebuild, version single-sourcing, README/API-SURFACE regen, alert() → toast, Windows path (35-39, 31-32).
Every fix batch lands judged (Codex min B) + full suite + live-curl verification before commit, per standing policy.