dashcaddy-api/VERSION is the deploy-time build stamp written by start.sh (the deployed commit SHA), NOT a semver source of truth. Batch 7's r1 edit set it to 1.17.0, which broke SelfUpdater.getLocalVersion() commit resolution (2 test failures: commit must end with a git SHA). Reverted to the pre-existing stamp 70e252c; version alignment claims corrected to package.json + openapi.yaml + README badge (all 1.17.0 — that half of the drift finding stands). VERIFY-OUTPUT.txt updated with the accurate transcript; audit finding 36 correction recorded in AUDIT-2026-10-27.md. Self-updater suites 13/13.
103 lines
13 KiB
Markdown
103 lines
13 KiB
Markdown
# 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
|
||
|
||
9. **MEDIUM — DNS check-update/update handlers hang.** `routes/dns.js:669-800`: bare `return`s, empty else/catch branches → request never answered until client timeout; non-JSON upstream dies in `JSON.parse` inside the swallowing catch. Fix: every path responds or throws typed.
|
||
10. **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.
|
||
11. **MEDIUM — Calendar ICS fetch timeout silently ignored** — `routes/calendar.js:177` passes `opts.timeout`, which `src/utils/http.js` explicitly strips (undici); effective deadline 5s not 15s. Fix: `fetchT(url, {}, 15000)`.
|
||
12. **MEDIUM — StateManager writes are non-atomic.** `state-manager.js:85,138` truncate-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.
|
||
13. **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.
|
||
14. **MEDIUM — WS auth fallback fails open.** `dashboard-ws.js:75-86` defaults to "any non-empty session cookie" when the real verifier isn't injected — the exact DC-061 footgun. Fix: default deny + loud log.
|
||
15. **MEDIUM — MCP `generate_caddyfile` auth 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.
|
||
16. **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 (no `response.ok` checks); plus install is all-or-nothing `addAll` of 36 URLs — one miss keeps the stale SW in control indefinitely.
|
||
17. **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.
|
||
18. **LOW — Calendar source update wipes the other credential type** (updating pass nulls token and vice versa) `routes/calendar.js:144-148`.
|
||
19. **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).
|
||
20. **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.
|
||
21. **LOW — MCP search_catalog drops the category filter when a query is present** (`mcp-core.js:304-311`).
|
||
22. **LOW — system_health/system_metrics tools bypass apiCall** — no timeout/size-cap/error envelope, unlike every other tool.
|
||
23. **LOW — Fleet tool 403s for free licenses with no premium hint in tools/list.**
|
||
|
||
## P2 — Frontend polish & races
|
||
|
||
24. **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.
|
||
25. **MEDIUM — openModal issues two overlapping fetch sequences** (no in-flight guard; last-response-wins can repaint with stale data).
|
||
26. **MEDIUM — Source toggle/delete handlers: unguarded fetch, no resp.ok check, optimistic re-render** — failures re-render as success.
|
||
27. **MEDIUM — `--cal-dot` theme 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.
|
||
28. **MEDIUM — Calendar chip keyboard-inaccessible; modal lacks Escape/click-outside/focus management** (`role=button` with no keydown; `display` set directly, bypassing wireModal).
|
||
29. **LOW — Calendar GETs bypass secureFetch** — no 15s timeout; one hung request wedges `refresh()` forever (pairs with 24).
|
||
30. **LOW — Polling ignores `document.hidden`** — 5-min calendar polls + 1s health polls run 24/7 in background tabs.
|
||
31. **LOW — log-insights.js still uses blocking `alert()` 6×** despite the toast convention.
|
||
32. **LOW — Windows path `C:\caddy\Caddyfile` prefilled on a Linux deployment** (`globals.js:39`).
|
||
33. **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)
|
||
|
||
34. **HIGH (credibility) — DC-166 branch never merged** (see P0-4): capabilities manifest and changelog advertise a toolset this branch doesn't ship.
|
||
35. **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.
|
||
36. **MEDIUM — Three-way version drift**: package.json 1.17.0 vs openapi.yaml 1.15.0. ~~VERSION file 1.15.0~~ CORRECTION (fix round): dashcaddy-api/VERSION is a BUILD STAMP (start.sh writes the deployed commit SHA at deploy time), not a semver source — it legitimately read 70e252c; setting it to a semver broke SelfUpdater commit resolution and was reverted. Real drift = openapi.yaml only (fixed to 1.17.0).
|
||
37. **MEDIUM — README quickstart endpoints omit `/v1` — every example 404s as written** (`README.md:283-300`).
|
||
38. **LOW — README says "50+ templates"; the measured catalog is 78** (DC-165 fixed marketing but not the repo README).
|
||
39. **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.
|
||
40. **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.
|
||
41. **LOW — Test-suite time bomb** (fixed this sweep): `calendar.routes.test.js` hardcoded `DTSTART:20261007` which 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-func` in test files, 0 production errors remaining (credentials.js control-regex self-resolved in the response-helper refactor). 1027 warnings mostly unused-vars noise — a `warnings → 0` pass would be a cheap professionalism win.
|
||
|
||
## Recommended execution order
|
||
1. **Merge `dc/DC-166-mcp-completion`** into this branch (fixes P0-4, shrinks P0-5, restores toolset truth) → re-run MCP suites.
|
||
2. **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).
|
||
3. **TOTP/JWT gate ordering** (P0-1) + a chain-level integration test that exercises the real middleware chain, not injected `req.auth`.
|
||
4. **CSP event-handler wiring** (P0-3).
|
||
5. **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).
|
||
6. **Reliability batch**: DNS hang, errorResponse arg order + grep gate, ICS timeout, atomic state writes, WS/tailscale fail-closed (9-14, 20).
|
||
7. **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.
|