Security
The Security dashboard — posture checklist, live traffic monitoring, and host hardening — plus the model behind it.
VAC's Security tab is "your box at a glance": a posture checklist, a live traffic-anomaly monitor, and read-only firewall/fail2ban state. It turns VAC's two best vantage points — Caddy's access log and VAC's own config and store — into one screen.
Nothing on this tab mutates host state. The posture checks and host readers are pure reads; the traffic monitor only holds bounded in-memory counters fed by the access log. VAC observes and advises — it doesn't reconfigure your firewall.
Posture checklist
A checklist that re-evaluates on each load. Each row is OK, a warning, or an error, with the reason and the fix. Host-hardening checks lead the list, since an exposed box with no firewall is the most dangerous state.
| Check | Flags when |
|---|---|
| Firewall | no firewall detected, or installed but inactive (error); state stale (warn). |
| fail2ban | not detected or inactive. |
| Master key | VAC_MASTER_KEY is missing (error — secrets can't be sealed). |
| Metrics token | VAC_METRICS_TOKEN is unset, leaving /metrics reachable if exposed (warn). |
| Exposure mode | running in local exposure rather than public HTTPS (warn). |
| Base domain | no base domain configured, so traffic isn't on HTTPS (warn). |
| Request monitoring | the Caddy access log isn't being read, so traffic analysis is off (warn). |
| Host ports | a service publishes a host port, bypassing Caddy (warn). |
Firewall and fail2ban checks depend on the host agent. If you deliberately
don't run one, opt out (VAC_SECURITY_EXPECT_FIREWALL=false / vac security-check) and the row
becomes an informational "disabled by operator" line instead of a warning.
Traffic anomaly monitor
Always on (VAC_SECURITY_MONITOR, default enabled) and cheap — it reads the access log VAC
already tails and keeps bounded per-IP counters. The panel shows:
- Top talkers — the busiest source IPs, with request and error counts.
- Recent requests — a live tail of what's hitting the box right now.
- Recent anomalies — a ring of the latest flagged events.
It flags two kinds of anomaly from a single IP within a sliding window:
| Signal | Default threshold |
|---|---|
| Request spike | 300 requests from one IP per minute |
| Error surge | 100 4xx/5xx responses from one IP per minute |
(Window, thresholds, and cooldown are tunable via VAC_SECURITY_* env vars.) A breach fires a
traffic_anomaly notification, debounced per
IP so you aren't spammed. Memory stays bounded: the per-IP table caps at ~1024 entries (least-recently-seen
evicted), so an attacker churning addresses can't grow it without limit.
Host security agent
The firewall and fail2ban panels are powered by an opt-in host agent — a small read-only collector that snapshots posture (active firewall backend, fail2ban jails and ban counts) for the dashboard. It's off by default because it installs a host-level component.
Enable it at install time (VAC_SECURITY_AGENT=true) or later:
vac security-agent onThe agent never bans, unbans, or changes firewall rules — it reports what's there. Acting on what you see (adjusting UFW, tuning fail2ban) stays a host-level task you do yourself.
The security model behind it
The dashboard surfaces a few deliberate design choices worth knowing:
- The control plane is sandboxed.
vac-apistays off thevac-edgenetwork, so your app containers — which run code you may not fully trust — can't reach the API that holds the Docker socket. See Architecture. - Secrets are encrypted at rest. Env vars, deploy keys, TOTP secrets, and webhook URLs are sealed with AES-256-GCM. See Environment variables.
- Auth has teeth. Operator login supports TOTP, and destructive actions require a recent step-up re-verification.
- Cookies are
Secureper request — set only when the request actually arrived over TLS, so thehttp://<host>:9393fallback stays usable without weakening real HTTPS traffic. - Outbound webhooks are SSRF-guarded — notification URLs resolving to private or loopback addresses are refused.
- Risky surfaces are opt-in — the in-dashboard container shell (
VAC_ENABLE_SHELL) and the host agent are off unless you turn them on.
API
The tab reads from four endpoints (session-authenticated, like the rest of the API):
GET /api/security/posture ## the checklist
GET /api/security/traffic ## live traffic snapshot
GET /api/security/firewall ## firewall state (needs the host agent)
GET /api/security/fail2ban ## fail2ban state (needs the host agent)