Watchdog: Security and Privacy
Access, agent tokens, stored credentials, required privileges (Docker socket, pid host, host mount), security headers and the data Watchdog stores and moves.
Watchdog: Security and Privacy
Access to the UI
| Topic | Behaviour |
|---|---|
| Method | HTTP Basic Auth with realm Watchdog |
| User | Exactly one admin user from APP_ADMIN_USER and APP_ADMIN_PASSWORD_HASH. There is no user management and no roles |
| Scope | All pages, including imprint and privacy notice. Exceptions: /api/ with its own token authentication and, in development mode (APP_ENV=dev), the paths /_profiler, /_wdt, /assets and /build |
| Password storage | Bcrypt hash in the env file, not in the database |
| Brute-force protection | Watchdog does not limit login attempts. Add a limit in the reverse proxy if needed |
Basic Auth sends the user name and password with every request. Run Watchdog behind HTTPS only. The
Strict-Transport-Securityheader is only set on secure requests.
On stage and in the local stack the app runs with
APP_ENV=dev. There the Symfony profiler and the debug toolbar (/_profiler,/_wdt) are reachable without signing in, because the firewall exempts these paths from authentication. They expose requests, configuration and environment values. Only the live stack (APP_ENV=prod) really protects all pages. Do not expose stage and local instances to the network unprotected.
Agent tokens
- A token consists of 64 hex characters (32 random bytes) and is shown only once.
- The database stores only the SHA-256 hash. The comparison uses
hash_equals. - Every request carries the token as
Authorization: Bearer .... - With Regenerate Token you invalidate the old token immediately.
- 60 requests per minute and IP are allowed on
/api/, after that Watchdog answers with429. - An agent can only report results for checks assigned to it. Watchdog accepts at most 500 results per request. Timestamps must lie between 5 minutes in the past and 1 minute in the future, otherwise server time applies.
Through /api/v1/agent/config the agent receives the complete configuration of its checks, including stored passwords of database checks. Protect the token like a password.
Stored credentials
| Data | Location | Form |
|---|---|---|
| Admin password | Env file | Bcrypt hash |
| BasicAuth credentials of monitored URLs | Table client_urls |
Plain text |
| Database password of a database check | Table site_checks, column config |
Plain text |
| Agent token | Table agents |
SHA-256 hash |
| SMTP access | MAILER_DSN in the env file |
Plain text |
Create a dedicated minimal-privilege user for database checks. Back up and protect the database file var/data.db accordingly.
Container privileges
| Container | Runs as | Access |
|---|---|---|
app |
www-data (UID 82) on stage and live |
its own data volume |
worker, scheduler |
Default user of the image (no user: set) |
Data volume and Docker socket (mounted read-only, the Docker API remains usable through it) |
agent |
www-data |
Host filesystem read-only under /host/root, pid: host, optionally the Docker socket |
What follows from this:
- The Docker socket enables Docker checks but means far-reaching privileges on the host. The Docker Exec check additionally runs freely configurable commands in containers.
pid: hostand the mount of/make any container escape from the agent more dangerous. Remove both if you do not use the related checks.- In
compose.stage.ymlandcompose.live.ymlthe agent container also receives the complete env file (env_file), including the admin hash,APP_SECRETandMAILER_DSN. This does not apply to remote agents viadocker-compose.agent.yml, which contain only the two agent variables. - The local stack
compose.ymlpublishes app and Mailpit on all network interfaces (8087,8128). Use it on a development machine only.
HTTP headers and browser protection
Every response carries X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, a Permissions-Policy without camera, microphone and location, and a Content-Security-Policy. The policy allows inline scripts (unsafe-inline) because the templates contain inline scripts. State-changing actions are protected by CSRF tokens.
Reverse proxy
Set TRUSTED_PROXIES to your proxy's IP, never to *. In compose.stage.yml and compose.live.yml Compose hard-sets the value to REMOTE_ADDR, so the app trusts the directly connected peer. The app listens on 127.0.0.1 only there.
Stored data and privacy
| Data | Personal data | Purpose |
|---|---|---|
| Contacts (name, email address) | yes | Recipients of alert emails |
| Clients, URLs, labels | depends on the content | Monitoring targets |
| Results (status, message, HTTP code, response time, time) | no, the message may contain host names, paths and error text | History and alerting |
| Agents (name, last contact, token hash) | no | Assignment of agent checks |
| Server logs | IP addresses of browsers | Operation |
Watchdog sends no data to third parties and contains no telemetry. Outgoing connections go to the monitored targets, to the SMTP server and, for agents, to the dashboard. Hosting, the retention period of results (see Configuration) and the legal texts are your responsibility. The bundled imprint and privacy pages are placeholders.
Reporting vulnerabilities
Do not report vulnerabilities as a public issue. Use the private vulnerability reporting of the GitHub repository (Security > Report a vulnerability).