WatchTower is a solo-maintained project. Security fixes are applied to the latest version on main only. Older tagged releases are not backported.
| Version | Supported |
|---|---|
| 3.x (latest) | yes |
| < 3.0 | no |
Please do not open a public GitHub issue for security vulnerabilities.
Use GitHub's private vulnerability reporting instead: Report a vulnerability
Include as much detail as you can - steps to reproduce, affected component, and potential impact. I aim to respond within 7 days and will keep you updated as the issue is investigated and fixed.
- SMTP credential handling in the settings API
- SMTP command injection or header injection via alert inputs
- Unauthenticated access to monitor data or alert credentials via the API
- Server-Side Request Forgery (SSRF) via monitor targets
- XSS in the dashboard or embedded views
- Dependency vulnerabilities with a credible exploit path
- The network WatchTower is deployed on - securing access to port 3000 is the operator's responsibility (firewall, reverse proxy with auth, VPN)
- SQLite file permissions - the host filesystem is the operator's responsibility
NET_RAWcapability required for ICMP - this is intentional and documented- Denial of service via resource exhaustion on self-hosted infrastructure
- Vulnerabilities in browsers used to access the dashboard
WatchTower has no built-in authentication. It is designed to run on a trusted internal network or behind a reverse proxy that handles access control. Exposing it directly to the public internet without additional protection is not recommended.
Alert credentials (Telegram token, SMTP password, Twilio auth token) are stored in plaintext in the SQLite database. Ensure the data/ volume is not readable by untrusted users on the host.
HTTP and API checkers, plus the webhook alerter, resolve the target hostname via DNS before connecting and block addresses in:
- Loopback (
127.0.0.0/8,::1) - RFC-1918 private ranges (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16) - Link-local (
169.254.0.0/16,fe80::/10) - Unique-local IPv6 (
fc00::/7)
ICMP and TCP checks bypass this guard by design. Both return only latency and up/down status — no response body can be exfiltrated. This allows monitoring internal hosts (routers, NAS, etc.), which is a core homelab use-case.
Secret fields are returned as "***" in all API responses. Sending "***" back in a PUT request is treated as "no change" so credentials are never round-tripped through the browser. Affected fields: telegram_token, email_smtp_pass, twilio_auth_token, twilio_account_sid, webhook_url, monitor authPass and authToken, and any module field whose key ends in _token, _key, or _secret.
checkTypeis validated againsthttp | tcp | icmp | apion create and updateintervalmust be an integer ≥ 30 seconds on create and update- Monitor labels and targets are HTML-escaped before interpolation into email alert bodies
- Error strings stored in the database are capped at 256 characters
- All mutating API routes (
POST,PUT,PATCH) requireContent-Type: application/json; requests without it receive 415. This prevents form-based CSRF. - Test-channel endpoints are rate-limited to one request per channel per 30 seconds.
Every response includes X-Content-Type-Options: nosniff and Referrer-Policy: strict-origin-when-cross-origin.
X-Frame-Options is intentionally omitted — the embed feature requires the app to be frameable. A full CSP is deferred until Tailwind is bundled at build time rather than loaded from CDN.
The /embed and /embed/monitor/:id routes are unauthenticated by design for public status-page use. If monitor names and targets are sensitive, protect the port at the network level or block /embed paths at the reverse proxy.