Files
dashcaddy/AUDIT-2026-10-27.md
T
Hermes ecbb34ee39 [grade=pending-urn] fix(DC-170): restore VERSION build-stamp semantics (batch 7 regression)
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.
2026-10-29 02:34:37 -07:00

103 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.