| Version | Supported |
|---|---|
| 1.4.x (latest: v1.4.1) | ✅ |
| 1.3.x | ❌ |
| 1.2.x | ❌ |
| 1.1.x | ❌ |
| 1.0.x | ❌ |
| older releases | ❌ |
Do not open a public GitHub issue for security vulnerabilities.
Report via:
- GitHub Security Advisory: New Advisory
- Email: miguel@developmi.com (include
[SECURITY]in subject)
Include in your report:
- Description of the vulnerability and its potential impact.
- Steps to reproduce or a proof-of-concept.
- Affected versions.
- Any suggested mitigations.
| Stage | Target |
|---|---|
| Acknowledgment | 48 hours |
| Triage and severity assessment | 5 business days |
| Fix and coordinated disclosure | 30 days (critical), 90 days (others) |
This project follows coordinated disclosure. We ask that you give us reasonable time to address the vulnerability before public disclosure:
- Critical issues: fix or mitigation targeted within 7 days; disclosure only after the fix is available.
- Other issues: disclosure no earlier than the fix or mitigation, within the 30/90-day response window.
We will credit reporters in the release notes unless anonymity is requested.
In-scope for this project:
- Authentication bypass or token leakage
- Arbitrary file write outside
ui-managed/volume - Token or secret exposure in logs or HTTP responses
- Directory traversal in file path construction
- SSRF via Caddy Admin API URL parameter
Out of scope:
- Vulnerabilities in Caddy, Coraza, or OWASP CRS (report to their respective projects)
- Issues requiring physical access to the host
- Social engineering
Images are signed with Cosign (keyless, GitHub OIDC):
cosign verify \
--certificate-identity "https://github.com/Developmi/caddy-waf-ui/.github/workflows/docker-build-scan-sign.yml@refs/heads/main" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/developmi/caddy-waf-ui@sha256:<digest>Always pull by digest in production. Do not use :latest in production compose files.
The UI reloads Caddy through POST /load on the Admin API (CADDY_ADMIN_URL,
default http://caddy-waf:2019). The Admin API call carries no credentials:
Caddy's plaintext admin endpoint has no built-in authentication, and this UI
does not (yet) present a client certificate.
Accepted residual risk - until the mTLS target (cross-project contract §6, caddy-waf ROADMAP items 6–7) is implemented, the deployment MUST enforce all of these compensating controls:
- Network isolation only: the Admin API is reachable only over the
internal Docker network (never host-published; compose publishes 80/443
only). The admin endpoint binds inside the container network
(
admin 0.0.0.0:2019 { enforce_origin }), never on the host - see the compose + bind-mount deployment model in INTEGRATION.md. - Platform default is
admin off(AC-3):caddy_admin_enabledisfalseunless the UI is actually deployed; when enabled, the rendered admin block isadmin 0.0.0.0:2019 { enforce_origin }inside the container network, never published to the host. - Host firewall denies 2019/2020 on all interfaces (stack L2 UFW/nftables
rules) and a fail-safe assert forbids those ports in
allowed_ports. CADDY_ADMIN_URLis operator-set and must point at the internal address; it is never exposed to end users.- Read-back verification (D3): after every
POST /loadthe UI verifies the live config viaGET /config/apps/http/servers; a mismatch fails loud and triggers the overlay rollback path - a compromised reload cannot silently disable the WAF without being reported.
Any deployment that cannot enforce controls 1–4 must NOT enable the UI.
The mTLS target (identity + access_control public keys on Caddy's remote
admin listener, UI client certificate) removes the reliance on network
placement alone.
The UI listens on 0.0.0.0:8080 by default (SC-8 row of the README "Security
Model" table). In a bare deployment without a host firewall this exposes the
UI to every client on the LAN over plain HTTP: the UI does not terminate TLS
itself — HTTPS is terminated by the reverse proxy in front of it (the
documented deployment fronts the UI with Caddy reverse_proxy + mTLS, or
restricts host exposure via the compose port mapping).
Why it matters: the session cookie is marked Secure, which browsers only
honor in a secure context (HTTPS; loopback is exempt, LAN addresses are not).
Served over plain HTTP on a LAN address, the cookie is not honored and the
bearer token used by /api/* crosses the LAN in cleartext — a LAN attacker
can sniff admin traffic.
Recommended mitigation — narrow the listener when LAN-wide access is not needed:
- Loopback-only access (UI and browser on the same host):
CADDY_UI_BIND=127.0.0.1:8080.Securecookies keep working over HTTP loopback (secure context). - Tailnet-only remote access: bind to the Tailscale interface IP
(
CADDY_UI_BIND=<tailscale-ip>:8080, shown bytailscale ip -4) — remote traffic then rides the encrypted Tailnet only. The documented Tailscale pattern remains a valid option and is unchanged. - TLS fronting: when the UI must be reachable beyond loopback/Tailnet, keep it behind Caddy+mTLS as documented.
The 0.0.0.0:8080 default is intentionally unchanged; exposure is restricted
per deployment via CADDY_UI_BIND.
Documented design decisions, not defects:
- Logout is client-side only. The session token is stateless: a
high-entropy bearer token compared in constant time
(
subtle.ConstantTimeCompare), with no server-side session store. Logout clears the cookie but cannot revoke or expire tokens already issued - anyone who captured a valid token keeps using it until the service redeploys with a rotatedCADDY_UI_TOKEN. This is the accepted trade-off of a single-operator admin console: treat the token as full admin access and rotate it when it may have been exposed. - Native sliding token-bucket rate limiting.
POST /loginis throttled to 5 requests/minute per client IP (burst 5) with a global ceiling of 60 requests/minute./api/*is throttled to 120 requests/minute per client IP (burst 30). Requests exceeding limits receive HTTP 429 withRetry-After. Perimeter rate limiting in Caddy (therate_limitdirective) acts as defense-in-depth before traffic reaches the UI sidecar.