Audit Logging¶
The Control Center implements structured audit logging inspired by NVIDIA NeMo Guardrails' rail activation tracking. Every security-relevant action is logged with timestamp, severity, and decision details.
Design Philosophy¶
NeMo Guardrails tracks every "rail" that fires when processing LLM requests — input validation, output filtering, topic boundary enforcement. We adopted this pattern for infrastructure actions:
- Every firewall change is a "rail activation"
- Every profile switch is a "policy decision"
- Every killswitch toggle is a "critical event"
- The log provides a complete audit trail for post-incident analysis
Log Format¶
Events are stored in JSON-lines format at /app/data/audit.jsonl:
{"ts": "2026-04-01T14:33:43.839686+00:00", "event": "system.started", "severity": "info", "details": {"firewall_enabled": true, "killswitch": false, "rules_count": 2}}
{"ts": "2026-04-01T14:35:12.123456+00:00", "event": "firewall.profile_applied", "severity": "info", "details": {"profile": "minimal", "rules_count": 3}}
{"ts": "2026-04-01T14:40:01.654321+00:00", "event": "firewall.killswitch", "severity": "critical", "details": {"activated": true}}
Each line is a self-contained JSON object. This format is:
- Easy to parse with standard tools (
jq,grep, Python) - Appendable without loading the entire file
- Compatible with log aggregation systems
Event Types¶
System Events¶
| Event | Severity | Details |
|---|---|---|
system.started |
info | firewall_enabled, killswitch, rules_count |
Firewall Events¶
| Event | Severity | Details |
|---|---|---|
firewall.rule_added |
info | rule_id, description, protocol, port, destination |
firewall.rule_deleted |
info | rule_id, description |
firewall.rule_updated |
info | rule_id, changed fields |
firewall.killswitch |
critical (on) / warning (off) | activated |
firewall.toggled |
warning (off) / info (on) | enabled |
firewall.profile_applied |
warning (unrestricted) / info | profile, rules_count |
firewall.force_applied |
info | — |
DNS Events¶
| Event | Severity | Details |
|---|---|---|
dns.blocking_toggled |
info | blocking state |
Route Events¶
| Event | Severity | Details |
|---|---|---|
routes.added |
info | subdomain, vm_port |
routes.removed |
info | route_id, subdomain |
Severity Levels¶
| Level | Color | Examples |
|---|---|---|
info |
Blue | Startup, rule added, profile applied |
warning |
Orange | Firewall disabled, killswitch deactivated, unrestricted profile |
critical |
Red | Killswitch activated |
Storage Management¶
Rotation¶
The audit file rotates automatically:
- Trigger: File exceeds 2 MB
- Action: Keep only the last 5,000 lines
- Check: After every write
Thread Safety¶
All file operations are protected by a threading lock:
_lock = threading.Lock()
def log_event(event, details, severity):
with _lock:
with open(AUDIT_FILE, "a") as f:
f.write(line)
_maybe_rotate()
API¶
GET /api/audit/events?limit=200&severity=¶
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
int | 200 | Maximum events to return |
severity |
string | — | Filter by severity level |
Returns events in chronological order (oldest first). The frontend reverses them for display (newest first).
Command-Line Access¶
You can also query the audit log directly: