- openClawRoutes was mounted at root causing /status vs /openclaw/status mismatch
- ctx.docker is a typed wrapper {client,pull,...} — all calls now use docker.client.*
- templates/deploy/removal/restore sub-routers had /apps/ hardcoded in inner routes
causing double-stacking when mounted under /apps (→ /apps/apps/templates etc)
- openclaw.js: GET /status, POST /deploy, GET/POST /proxy/*, DELETE /
1. WebSocket exec auth bypass (exec.js): Require valid JWT or API key
before accepting WebSocket upgrade. Reject unauthenticated requests
with 401 before the upgrade completes.
2. Shell injection in router auto-login (session-handlers.js): Validate
baseUrl against safe hostname pattern before embedding in wget shell
command. Reject with null session if invalid.
3. Path traversal in credentials routes (services.js): Add explicit
serviceId validation (alphanumeric + dash/underscore/dot, max 100
chars) to all three credential endpoints. Removed redundant
try/catch wrapper.
4. execSync injection in CA CSR generation (ca.js): Add sanitize step
replacing any non-alphanumeric domain chars with underscore before
interpolation into shell subj argument. Redundant with existing
validation but provides defense-in-depth.
5. Auth bypass when TOTP disabled (middleware.js): Split the logic
cleanly — disabled TOTP means no auth (initial setup state), enabled
TOTP means all auth methods checked (session/JWT/API key). Removed
the sessionDuration:never conflating shortcut.
- Set Domain=.sami on session + CSRF cookies so browsers send them to all subdomains
- This fixes Caddy forward_auth returning 401 for radarr/sonarr/prowlarr
- Fix login URL concatenation bug (radarr.samilogin -> radarr.sami/login)
- Fix getSetCookie() missing from _httpsFetch/_httpFetch response objects
- Fix array/string handling for set-cookie header in session-handlers fallback
- Refactor csrf-protection to createCSRFMiddleware() factory with cookieDomain support
- Pass renewCSRFToken through middleware deps chain to TOTP route
The /api/v1/services/status endpoint (dashboard card ON/OFF) uses an
HTTPS agent to probe each service. When /app/pki/root.crt is missing
inside the container, it fell back to new https.Agent() which rejects
self-signed certificates. This caused all .sami domain probes to fail
with UNABLE_TO_GET_ISSUER_CERT_LOCALLY, making dashboard cards randomly
flip between ON and OFF depending on whether the Pylon relay responded
before the 10s deadline.
Fix: use rejectUnauthorized: false as fallback when CA cert is absent.
- 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>
- 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).
Dockerfile never received DASHCADDY_COMMIT at build, so /app/VERSION held
'unknown'. _isNewer then treated same-version-different-commit as newer,
making the auto-updater rebuild the container indefinitely (each rebuild
still produced commit='unknown').
- self-updater._isNewer: normalize commits; treat unknown/null/empty as no
commit info and fall back to pure version comparison
- self-updater._autoCheckAndApply + routes/updates: refuse to apply when
local version >= remote version (belt-and-suspenders)
- update-management.js: hide '(unknown)' from version label
- Dockerfile: COPY VERSION instead of writing from build arg
- VERSION: committed placeholder ('dev'); scripts/release.sh now writes
the real short SHA into the tarball's VERSION before tar-ing, so every
published release ships with an accurate commit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cloud backups (Dropbox / WebDAV / SFTP):
- backup-manager.js: save + load handlers per provider, credential
resolution via credentialManager, destination probe.
- routes/backups.js: /credentials/{provider} (masked GET, POST, DELETE),
/test-destination, scheduling endpoints.
- status/js/backup-restore.js: destination picker, provider-specific
credential forms, test button wired to backend probe.
- npm deps already present (dropbox 10.34.0, webdav 5.7.1,
ssh2-sftp-client 11.0.0).
Resource history:
- resource-monitor.js: three-tier rollup storage — raw 10s samples
(7-day retention), hourly rollups (30-day), daily rollups
(365-day). getHistoryByRange() auto-selects the appropriate tier.
- routes/monitoring.js: /monitoring/history/:containerId now supports
startTime/endTime range mode (legacy ?hours=N still works).
- status/js/resource-monitor.js + dashboard.css: "History" tab with
range buttons (1h/24h/7d/30d/1y), SVG sparklines for
CPU / memory / network. Renderer handles raw and rolled-up shapes.
status/dist/features.js rebuilt from source via build.js.
Lifted out of wip/cloud-backups-and-history; the half-finished
app-deps feature from that branch (frontend calls /api/v1/apps/
check-dependencies but the endpoint doesn't exist) is preserved
separately on wip/app-deps for later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- health.js: replace magic number 5000 with TIMEOUTS.HTTP_DEFAULT (twice)
- services.js: replace magic number 5000 with TIMEOUTS.HTTP_DEFAULT
Both files already import TIMEOUTS from constants but weren't using it.
- monitoring.js: Added log dependency, replaced console.log with log.warn
- themes.js: Added log dependency, replaced console.error with log.error
- src/app.js: Pass log to monitoringRoutes and themesRoutes
This fixes error messages being lost to stdout instead of proper log 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>
- Remove redundant ctx shim that conflicted with function parameter
- Use destructured notification/safeErrorMessage directly
- Add pylon, customLogoDark, customLogoLight to known config keys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The modular refactor changed function signatures to destructured deps but
left internal ctx.* references intact, causing "ctx is not defined" errors
on /api/config, /api/logo, and many other endpoints. Also implements
loadTotpConfig and saveTotpConfig which were left as stubs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add missing fetchT dep to session-handlers.js (fixes ctx is not
defined error that broke jellyfin/emby/plex/syncthing SSO)
- Replace all ctx.fetchT calls with direct fetchT usage
- Remove server-old.js (69K monolith backup) from tracking
- Remove AI-generated doc artifacts from repo root
- Update .gitignore to prevent re-adding these files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Consolidated all error classes into single errors.js
- Removed duplicate error definitions (NotFoundError, etc.)
- Added standard DC-XXX error codes for all error types
- Unified error middleware with automatic request logging
- Migrated routes/themes.js to throw-based error pattern
- Updated routes/services.js to use ConflictError
- Cleaner server.js error handler registration
- 40% less error handling boilerplate in routes
- Consistent error response format across all endpoints
- Replaced god object ctx with explicit dependency injection
- Added JSDoc documenting required dependencies (8 deps vs 50+)
- Updated response calls to use response-helpers (success/error)
- Self-documenting: you can see exactly what this route needs
- Health checks, pylon relay, CA cert validation all preserved
- Replaced god object ctx with explicit dependency injection
- Added JSDoc documenting all required dependencies
- Updated response calls to use response-helpers (success/error)
- Maintained all existing functionality
- Self-documenting: you can see exactly what this route needs
- Easier testing: mock only what's actually used (14 deps vs 50+ ctx properties)
- serviceUrl() now checks service.url before falling back to buildServiceUrl(id)
- Service update no longer overwrites ID with the new subdomain
- Accept "localhost" as valid IP in service update validation
- Find services by ID or URL match when updating
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The dashboard uses /api/v1/services/status (not /api/health/services)
for live status cards. This endpoint was missing pylon relay fallback,
so services unreachable from the Docker container showed as OFF even
when the pylon was running. Also adds Windows VBS startup wrapper for
pylon persistence across reboots.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DashCaddy Pylon is a lightweight probe agent that runs on remote
networks to relay health checks for services the main DashCaddy
instance can't reach directly (e.g., .sami domains, LAN IPs).
- Standalone zero-dependency Node.js script (pylon/dashcaddy-pylon.js)
- Optional API key auth, HEAD→GET fallback, batch probe support
- Health routes now try direct check first, fall back to pylon relay
- New endpoints: /health/probe (act as pylon), /health/pylon (status)
- Config: add "pylon": { "url": "...", "key": "..." } to config.json
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix service edit double-write bug (was creating duplicate entries)
- Add editable display name field to service edit modal
- Backend update endpoint now accepts name, logo, and recalculates url
- Fix CSRF token regeneration breaking all POST requests (nonce was
being regenerated on every request, invalidating cached tokens)
- CSRF nonce now persists across requests, rotated only on TOTP login
- Frontend secureFetch auto-retries on CSRF failure with fresh token
- Restore lifetime license activation on DNS2
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>
- Add url-resolver.js with single resolveServiceUrl() used by all 5 consumers
(probes, health routes, health checker auto-config)
- Health checker now does full sync (add/update/remove) instead of add-only,
and re-syncs automatically after every services.json mutation
- docker-maintenance and log-digest are now optional imports with try/catch,
preventing container crashes when these files are absent
- Add null guards in routes/logs.js for graceful 503 responses
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mount Tailscale router at /tailscale prefix so all 10 routes resolve
to /api/tailscale/* as expected by middleware, audit logger, and
frontend. Previously 5 routes (status, config, check-connection,
devices, protect-service) resolved to /api/* instead, with config
colliding with the settings route. Strip redundant /tailscale/ prefix
from OAuth routes that were compensating for the missing mount prefix.
Increase default health check timeout from 10s to 20s to reduce false
positives on slower services.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>