Skip to content

Security: Developmi/caddy-waf-ui

SECURITY.md

Security Policy - caddy-waf-ui

Supported Versions

Version Supported
1.4.x (latest: v1.4.1) ✅
1.3.x ❌
1.2.x ❌
1.1.x ❌
1.0.x ❌
older releases ❌

Reporting a Vulnerability

Do not open a public GitHub issue for security vulnerabilities.

Report via:

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.

Response timeline

Stage Target
Acknowledgment 48 hours
Triage and severity assessment 5 business days
Fix and coordinated disclosure 30 days (critical), 90 days (others)

Disclosure Policy

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.

Scope

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

Supply Chain Verification

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.

Caddy Admin API - Residual Risk (contract §6)

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:

  1. 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.
  2. Platform default is admin off (AC-3): caddy_admin_enabled is false unless the UI is actually deployed; when enabled, the rendered admin block is admin 0.0.0.0:2019 { enforce_origin } inside the container network, never published to the host.
  3. Host firewall denies 2019/2020 on all interfaces (stack L2 UFW/nftables rules) and a fail-safe assert forbids those ports in allowed_ports.
  4. CADDY_ADMIN_URL is operator-set and must point at the internal address; it is never exposed to end users.
  5. Read-back verification (D3): after every POST /load the UI verifies the live config via GET /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.

UI Bind Default - LAN Exposure (SC-8)

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. Secure cookies keep working over HTTP loopback (secure context).
  • Tailnet-only remote access: bind to the Tailscale interface IP (CADDY_UI_BIND=<tailscale-ip>:8080, shown by tailscale 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.

Known Limitations - Auth Notes & Rate Limiting

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 rotated CADDY_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 /login is 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 with Retry-After. Perimeter rate limiting in Caddy (the rate_limit directive) acts as defense-in-depth before traffic reaches the UI sidecar.

There aren't any published security advisories