Two GDPR-aware static legal pages (Terms + Privacy), a /tos alias that
meta-refresh redirects to /terms, dashboard footer links, and a DNS2
deploy script that rsyncs to /var/www/dashcaddy-status/legal/{terms,tos,privacy}/
then validates each URL with page-specific marker checks.
Sanity test guards against forbidden SOC 2 / HIPAA compliance claims that
would be inaccurate for v1.0 launch. Regex covers SOC[ -]?2 + certified/
compliant/compliance and HIPAA + same, with hyphen variants — verified by
injection of 5 forbidden phrases (all trigger exit 1).
Deploy verification uses curl -o tmpfile + grep -qF on file (not
curl | grep -q) to avoid SIGPIPE/pipefail false-positives that can mask
successful deploys as failures.
Routes: status.sami/legal/{terms,tos,privacy}
Aspirational legal.dashcaddy.net subdomain deferred to v1.x — needs DNS,
Caddy vhost, LE cert infra. Single canonical host covers launch.
Co-graded: Codex A urn:ump:khq6a3lwjwdkhd2hqwtds5pppzb7s2ft3t73sj5cz2hwgmb44owq
Introduces a unified security event store and HTTP API that ingests events
from any of the configured sources (API audit, Caddy access log, fail2ban,
shared_bans, future remote agents) and surfaces them in the dashboard.
New files:
src/security/event-store.js JSONL-backed store + in-memory query index
src/security/host-registry.js Registered hosts with per-host API keys
src/security/event-workers.js Tail-followers for Caddy/fail2ban/shared_bans logs
routes/security.js Events, hosts, ingest, SSE stream endpoints
status/js/security-center.js Dashboard modal with Overview/Events/Hosts tabs
SECURITY-FEATURE.md Full feature documentation
DEAD-CODE.md, DUP-CODE.md, HARDENING.md Prior audits
Modified:
src/app.js Mount /api/v1/security/*
src/utilities/middleware.js Add ingest endpoints to PUBLIC_ROUTES
src/security/audit-logger.js Mirror audit events into security store
server.js Start security workers on boot
status/build.js Bundle security-center.js
status/index.html Add Security button to nav
The DC-027 rate limiter on /api/v1/auth/* shipped with skip: () => isTest,
which counted every request — including those from a logged-in TOTP session.
Caddy's forward_auth fires /auth/gate/* on every page-load asset (HTML, JS,
CSS, XHR), so a normal browser session exhausted the 20-req/15-min budget
within ~3 page loads and started getting 429 'Too many auth requests' even
with a valid session cookie.
Fix: extend skip to also return true when req.auth.type is 'session',
'jwt', or 'apikey' (set by jwtApiKeyAuthMiddleware, which runs upstream
of the limiter). The unauthenticated path is still rate-limited — DC-027's
credential-scraping defense is preserved.
Also closes the uncommitted working-tree changes for:
- DC-026: routes/auth/sso-gate.js — pre-auth check in buildLoginPage,
redirected error fallbacks to status.sami?auth=required&return=...
- DC-022: dashcaddy-api/VERSION bumped to fef7e07
- status/index.html + status/js/tailscale-devices.js — Tailscale device card
4 new regression tests pin the fix:
- skips when req.auth.type === 'session'
- skips when req.auth.type === 'jwt'
- skips when req.auth.type === 'apikey'
- still counts UNAUTHENTICATED requests (defense preserved)
Live verified: 50/50 authenticated /auth/gate/plex calls passed (was
20/30 before fix). plex.sami/dashcaddy-login returns 200 with no redirect
loop. Plex auto-login token round-trips end-to-end.
- status/js/totp-recovery.js: NEW. Wires up recovery panel on the TOTP
gate. Pastes Base32 -> /api/v1/totp/setup -> /verify-setup -> session.
Exposes window._refreshRecoveryLink() called by totp-auth.js.
- status/js/totp-auth.js: showTotpOverlay() now calls
_refreshRecoveryLink() so the recovery link hides when TOTP is healthy
and appears when it's broken.
- status/js/totp-settings.js: removed setupSection.style.display='none'
so 'Import existing secret' is always visible; added 'Download backup
file' button after setup that exports the Base32 + recovery
instructions as JSON.
- status/index.html: added 'Lost access? Recover with saved Base32
key ->' link to the TOTP overlay plus the recovery panel itself;
added title tooltip to the auth card reminding users to save the
Base32 on first setup.
- status/build.js: include JS('totp-recovery.js') in the core bundle
after totp-auth.js (since recovery registers a hook auth calls).
- Remove legacy /api/ mount; all routes now under /api/v1/ only
- Update path matchers (CSRF excludes, public routes, audit log, rate limits)
- Move standalone routes (/api/network/ips, /api/docs, /api/docs/spec) to v1
- Update openapi.yaml (110 paths), CA pages, and 4 lingering frontend files
- Add LICENSE (proprietary EULA), CHANGELOG.md (Keep a Changelog format)
- Add .gitea/workflows/ci.yml (test+lint and security audit jobs)
- Fix 9 pre-existing no-empty lint errors so CI starts green
- Drop ad-hoc scratch reports and *.bak files from repo root
All 739 jest tests pass. Lint is clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the build runs on a Windows checkout, fs.readFileSync returns the
file with CRLF intact, and the hash of the inline bootstrap script's
body reflects those CRLFs. The release tarball / git transport / Linux
file system strip CRLF on the publishing host, so the browser sees the
LF-only version and computes a different sha256. CSP then blocks the
script — disabling the version widget, theme switcher, and any other
DOM bindings set up in that inline block.
Normalize CRLF -> LF before computing the hash (the on-disk file keeps
its native line endings; only the hash input is normalized). The CSP
allowlist now matches whatever Caddy actually serves.
- self-updater: per-instance notify secret (auto-generated), notifyAndApply()
triggers an immediate check+apply for the publishing host
- routes: POST /api/system/update-notify (X-DashCaddy-Notify-Secret gated,
added to public-routes allowlist so TOTP doesn't block machine-to-machine)
- dashcaddy-update.sh: include VERSION in backup/deploy/rollback copy lists;
belt-and-suspenders write trigger.json commit to VERSION post-deploy.
Fixes drift where /app/VERSION stayed at the old commit after self-update.
- release.sh: mirror failures are non-fatal+loud; HTTP-verify get2 after
rsync; auto-notify co-located instance via /opt/dashcaddy/updates/notify-secret
(or honour DASHCADDY_NOTIFY_TARGETS for multi-instance setups).
- Service Filter Bar: search by name, filter by status (online/offline)
- Batch Operations: multi-select containers for start/stop/restart
- Container Snapshots: create and manage Docker checkpoints
- Added filter bar and batch action bar to index.html
- Added snapshot button to Admin tools section
- New JS modules: service-filter.js, batch-operations.js, snapshot.js
- Updated build.js to include new modules in bundle
- New container-logs.js module for viewing Docker container logs
- Integrated with existing API endpoints (/logs/containers, /logs/container/:id, /logs/stream/:id)
- Features:
- Select container from dropdown
- View logs with stdout/stderr color coding
- Real-time log streaming via SSE
- Search/filter within logs
- Download logs as text file
- Line count and filter indicators
- Added '📜 Container Logs' button to Tools section in index.html
- Added to features.js bundle via build.js
- Rebuilt dist files
- Container exec/shell via WebSocket + xterm.js (subtle >_ button on cards)
- Live dashboard updates via SSE (resource alerts, health changes, update notices)
- Docker Compose import with YAML parsing, preview, and dependency-ordered deploy
- Volume & network management modal with disk usage overview
- CPU/memory resource limits on deploy and live update
- Email SMTP notifications (nodemailer) alongside Discord/Telegram/ntfy
- Scheduled auto-update scheduler with maintenance windows (daily/weekly/monthly)
New deps: ws, js-yaml, nodemailer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server export now includes encryption key, themes, and all config files.
Client export bundles all DashCaddy localStorage keys (19 named + dynamic
widget keys) as browserState. Restore handles both server and browser
state in one operation. Legacy v1.0 import format still supported.
Removed redundant Export/Import toolbar buttons — Backup modal is now
the single entry point.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Escape user-input port number in app-selector innerHTML
- Replace inline onclick with addEventListener in backup history (HTML entity decode bypass)
- Add Content-Security-Policy meta tag with script hash
- Replace document.write with textContent for footer year
- Filter __proto__/constructor/prototype in Object.assign calls
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apps can now be served at domain.com/appname/ instead of requiring
subdomain DNS records (appname.domain.com). Supports three subpath
modes per template: native (URL base env var), strip (handle_path),
and none (incompatible warning). Tested on Linux with deploy/removal
lifecycle verified.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Full codebase including API server (32 modules + routes), dashboard frontend,
DashCA certificate distribution, installer script, and deployment skills.