DC-080 round-1 GLM-5.3 judge verdict: B. Round-2 polish folded into same
commit per multi-round fix-first protocol: tighten tag regex to require
non-empty name after 'tag:' (matches Tailscale spec), drop dead
`module.exports.createApp = null` line.
THREAT MODEL
Pre-fix, /api/v1/tailscale/* and /api/v1/tailscale/admin/* (TOTP-gated)
had inconsistent checks on caller-supplied input. Three coupled gaps:
(a) PUT /settings validated `apiToken.startsWith('tskey-api-')` but
had NO length cap — body-parser limit was the only ceiling. A 1 MB
string starting with `tskey-api-` would be `.trim()`-ed, sent to
Tailscale's /devices endpoint, and waste server-side CPU on a
request that will always 401.
(b) POST /settings/test accepted `apiToken` from the body with NO
validation at all. The PUT route's prefix check did NOT extend to
this path. An operator could submit arbitrary junk and the
container would still call /devices on the Tailscale API with it
(DoS-reflection + fingerprint timing for an attacker probing
whether this API token format is accepted).
(c) POST /admin/keys validated `tags` as Array but NOT per-element
type — `tags: ['tag:guest', null, 123, {injection: true}]` would
be forwarded to Tailscale verbatim. Tailscale's API is JSON-strict
and would 400 the request, but the bad shape reached the wire.
Similarly `description` had no length cap (Tailscale caps at 120
chars per their docs).
All three are gated by TOTP — this is a logged-in-operator / phished-
session threat surface, not anonymous-unauth. The fix is defense-in-
depth: a bug in the auth path (TOTP bypass, session theft, future route
handler trust-boundary drift) should not turn these endpoints into a
`submit anything and forward to Tailscale` relay.
FIX 1 — Shared validators (round-1)
- `_validateApiToken(token)`: typeof string check, prefix required,
length cap 256 chars. Catches empty/null/non-string AND oversize.
- `_validateTags(tags)`: undefined/null allowed (optional field),
Array.isArray check, max 32 entries, per-element string check,
per-element length cap 64 chars, regex
`/^tag:[a-z0-9][a-z0-9_-]{0,62}$/` (round-2: requires non-empty
name after `tag:` per Tailscale spec).
- `_validateDescription(description)`: undefined/null allowed, string
type check, length cap 120 chars (matches Tailscale's documented cap).
All three return null on success or an error string on failure. Route
layer maps to 400 via `errorResponse`. Validators exported via
`module.exports._validators` for direct unit testing (otherwise
unreachable from outside the factory closure).
FIX 2 — Endpoint wiring (round-1)
- PUT /settings: replaced inline `!startsWith('tskey-api-')` check with
`_validateApiToken(token)`. Single source of truth for the rule.
- POST /settings/test: added `_validateApiToken(token)` guard BEFORE
calling `client.setApiToken(token)`. The body is optional, so the
guard is skipped when no token is provided (uses stored token path).
- POST /admin/keys: replaced `Array.isArray(opts.tags)` shallow check
with `_validateTags(opts.tags)`, plus `_validateDescription(opts.description)`.
Old code already validated `expirySeconds`; that stays.
FIX 3 — Round-2 polish
- TAG_KEY_RE: `/^[a-z0-9][a-z0-9:_-]{0,63}$/` → `/^tag:[a-z0-9][a-z0-9_-]{0,62}$/`.
The old regex accepted `tag:` (empty name), which Tailscale's API
rejects. New regex requires `tag:` prefix and ≥1 alphanumeric name
char followed by [a-z0-9_-]{0,62} — total length up to 67 chars, well
within Tailscale's documented 15..63 char tag length.
- Removed `module.exports.createApp = null` vestigial line — the file
only exports the factory function and the _validators bag.
TESTS (29 original + 16 new = 45 in this suite)
- 4 PUT /settings new: length cap, non-string type, prefix round-trip
(existing 'starts with' tests already passed), plus the original
6 (4 pre-existing PUT tests stay green).
- 4 POST /settings/test new: prefix rejection, length cap, stored-token
path with empty body still works.
- 4 POST /admin/keys new: null/123/object entries rejected, uppercase /
whitespace / CRLF rejected, description length cap, canonical
lowercase `tag:server` accepted.
- 4 direct validator unit tests: validateApiToken (5 cases incl. cap-edge),
validateTags (8 cases incl. round-2 bare-'tag:' rejection), validateDescription
(3 cases incl. cap-edge), constants-export surface.
All 45 tests pass on DNS2 (verified). Full repo suite unchanged: 2351/2351.
DashCaddy
Self-hosted dashboard for managing Docker apps with automatic SSL, DNS, and reverse proxy configuration.
What is DashCaddy?
DashCaddy is an all-in-one solution for self-hosting Docker applications. It combines:
- 🎨 Beautiful Dashboard - Monitor all your services in one place
- 🐳 Docker Management - Deploy 50+ pre-configured apps with one click
- 🔒 Automatic SSL - Internal CA with automatic certificate generation
- 🌐 DNS Integration - Automatic DNS record creation (Technitium DNS)
- 🔄 Reverse Proxy - Caddy configuration managed automatically
- 🔐 Tailscale Support - Secure remote access built-in
Features
Authentication & Security
- Built-in TOTP two-factor authentication
- Fine-grained access control per service
- Secure session management
- Group-based permissions
Dashboard
- Real-time service health monitoring
- Response time tracking
- Status indicators with visual feedback
- Weather widget
- Multiple themes (dark/light/blue)
- Import/export configuration
App Deployment
- 50+ pre-configured app templates
- One-click deployment
- Automatic DNS + SSL + reverse proxy setup
- Container health checking
- Deployment status tracking
- SSL certificate generation monitoring
Service Management
- Add/edit/delete services
- Restart containers
- View logs
- Update configurations
- Silent deletions (no annoying popups)
Developer Tools
- Error log viewer
- API endpoints for automation
- Import/export for testing
- Comprehensive error logging
Quick Start
Prerequisites
- Docker & Docker Compose
- Caddy web server
- Technitium DNS (optional, for automatic DNS)
- Node.js 18+ (for API server)
Installation
- Clone the repository
git clone https://github.com/yourusername/dashcaddy.git
cd dashcaddy
- Install dependencies
cd caddy-api
npm install
- Configure environment
cp .env.example .env
# Edit .env with your settings
- Start the API server
npm start
- Configure Caddy Add to your Caddyfile:
status.yourdomain.com {
root * /path/to/dashcaddy/status
file_server
reverse_proxy /api/* localhost:3001
}
- Access the dashboard
Open
https://status.yourdomain.comin your browser
Health Probes
DashCaddy exposes Kubernetes/Docker-standard health endpoints for container orchestration. No auth required — these are designed for orchestration tooling to poll.
| Path | Purpose | Returns |
|---|---|---|
/healthz or /health/live |
Liveness — is the Node.js process alive? | 200 with {status: "alive", uptime: <seconds>} |
/readyz or /health/ready |
Readiness — are critical deps reachable? (config file, services file, Docker daemon, Caddy admin API) | 200 if all OK, 503 if any dep fails (with details in the checks object) |
/health |
Backwards-compat alias for /healthz |
Same as /healthz |
When to use which:
- Use
/healthz//health/livein alivenessProbe— should the container be restarted? - Use
/readyz//health/readyin areadinessProbe— should traffic be routed to this instance?
Docker Compose healthcheck
Copy-paste this into your DashCaddy docker-compose.yml:
services:
dashcaddy-api:
image: ghcr.io/samiahmed7777/dashcaddy-api:latest
# ... your existing config ...
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3001/readyz', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
Kubernetes probes
livenessProbe:
httpGet:
path: /healthz
port: 3001
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
path: /readyz
port: 3001
initialDelaySeconds: 10
periodSeconds: 10
Both endpoints return JSON. Liveness is cheap (no I/O, no deps). Readiness touches the Docker daemon and Caddy admin API with a 3-second timeout each, so it's safe to poll every 10s without load concerns.
Configuration
Environment Variables
Create a .env file in the caddy-api directory:
# Caddy Configuration
CADDYFILE_PATH=/path/to/Caddyfile
CADDY_ADMIN_URL=http://localhost:2019
# DNS Configuration (optional)
DNS_SERVER=192.168.1.1
DNS_TOKEN=your-dns-token
# File Paths
SERVICES_FILE=/path/to/services.json
ERROR_LOG_FILE=/path/to/dashcaddy-errors.log
DNS Integration
DashCaddy works with Technitium DNS for automatic DNS record creation:
- Install Technitium DNS
- Create an API token with DNS management permissions
- Configure DNS credentials in dashboard (🔑 Tokens button)
Tailscale Integration
For secure remote access:
- Install Tailscale on your server
- Services can be restricted to Tailscale-only access
- Configure in deployment settings
Usage
Deploying an App
- Click "App Selector" button
- Choose an app from the template library
- Configure:
- Subdomain (e.g.,
jellyfin→jellyfin.yourdomain.com) - Port (auto-suggested)
- IP address (defaults to localhost)
- Tailscale-only access (optional)
- Subdomain (e.g.,
- Click "Deploy"
- Wait for SSL certificate generation (30-60 seconds)
- Access your app!
Managing Services
- View Status: Cards show real-time health and response times
- Open Service: Click "Open" button
- Restart: Click restart button (for Docker containers)
- Delete: Click delete button (removes everything: container, DNS, Caddy config)
- Edit: Click settings button to modify configuration
Viewing Error Logs
- Click "📋 Logs" button in toolbar
- View all errors with timestamps and context
- Refresh to see latest errors
- Clear logs when resolved
Backup & Restore
Export Configuration:
- Click "📤 Export" button
- JSON file downloads with all your services
- Save safely
Import Configuration:
- Click "📥 Import" button
- Select your backup JSON file
- Confirm import
- Dashboard reloads with restored configuration
Note: API tokens are not exported for security. Reconfigure after import.
App Templates
DashCaddy includes 50+ pre-configured templates:
Media & Entertainment
- Plex, Jellyfin, Emby
- Navidrome, Airsonic
- Tautulli, Overseerr
Downloads
- Sonarr, Radarr, Lidarr, Readarr
- Prowlarr, Bazarr
- qBittorrent, Transmission
- SABnzbd, NZBGet
Productivity
- Nextcloud
- Paperless-ngx
- BookStack, Outline
- Standard Notes
Management
- Portainer
- Homepage, Homarr
- Uptime Kuma
- Grafana
Security & Authentication
- Vaultwarden (Password Manager)
Development
- Gitea
- VS Code Server
- Jenkins, Drone CI
And many more!
API Endpoints
Services
GET /api/services- List all servicesPOST /api/services- Add servicePUT /api/services- Bulk import servicesDELETE /api/services/:id- Remove service
App Deployment
GET /api/apps/templates- List app templatesPOST /api/apps/deploy- Deploy new appDELETE /api/apps/:id- Remove deployed app
Error Logs
GET /api/error-logs- Get error logsDELETE /api/error-logs- Clear error logs
DNS Management
POST /api/dns/record- Create DNS recordDELETE /api/dns/record- Delete DNS record
Caddy Management
GET /api/caddy/config- Get Caddyfile contentPOST /api/caddy/reload- Reload Caddy configuration
Troubleshooting
SSL Certificate Errors
Problem: "Secure Connection Failed" when accessing new service
Solution:
- Wait 30-60 seconds for certificate generation
- Check dashboard notification for SSL status
- Manually reload Caddy:
caddy reload --config /path/to/Caddyfile - Check error logs in dashboard
DNS Not Resolving
Problem: Service URL doesn't resolve
Solution:
- Verify DNS server is running
- Check DNS credentials in 🔑 Tokens menu
- Manually add DNS record in Technitium DNS
- Flush DNS cache:
ipconfig /flushdns(Windows) orsudo systemd-resolve --flush-caches(Linux)
Container Won't Start
Problem: Deployment succeeds but service is offline
Solution:
- Check Docker logs:
docker logs [container-id] - Verify port isn't already in use
- Check container resource limits
- View error logs in dashboard
Import/Export Issues
Problem: Import fails or data is incomplete
Solution:
- Validate JSON format
- Check file has
versionandservicesfields - Reconfigure API tokens after import
- Check error logs for details
Development
Project Structure
dashcaddy/
├── status/ # Dashboard frontend
│ ├── index.html # Main dashboard
│ └── assets/ # Logos, icons, fonts
├── caddy-api/ # API backend
│ ├── server.js # Express server
│ ├── app-templates.js # App template definitions
│ └── package.json # Dependencies
├── dashcaddy-installer/ # Electron installer (WIP)
└── docs/ # Documentation
Adding Custom App Templates
Edit caddy-api/app-templates.js:
"myapp": {
name: "My App",
description: "Description of my app",
icon: "🚀",
logo: "https://cdn.example.com/logo.png",
category: "Productivity",
docker: {
image: "myapp/myapp:latest",
ports: ["{{PORT}}:8080"],
volumes: ["/opt/myapp:/data"],
environment: {
"APP_ENV": "production"
}
},
subdomain: "myapp",
defaultPort: 8080,
healthCheck: "/health"
}
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
Roadmap
- Service groups/categories
- Container log viewer
- DNS management UI
- Backup automation
- Multi-user support
- Mobile app
- Analytics dashboard
- Template marketplace
License
Proprietary software. All rights reserved. See LICENSE for the End-User License Agreement (EULA).
Credits
- Dashboard Icons: walkxcode/dashboard-icons (MIT License)
- Caddy: caddyserver.com
- Technitium DNS: technitium.com/dns
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Wiki
Acknowledgments
Built with ❤️ for the self-hosting community.
DashCaddy - Making self-hosting beautiful and effortless.