Overview
Vigilo's audit log records every state-changing action with cryptographic integrity. Each AuditLog row carries the previous row's hash, making the log a tamper-evident chain — a deleted or modified row breaks verification and is immediately visible. The log streams to external SIEM targets (CloudWatch, Elasticsearch, Splunk, syslog, generic webhooks) in real time (WC.5), and the UI presents human-readable diffs for any change (T2.6).
The audit page is at /ws/{slug}/governance/audit. It supports full-text search, faceted filters (actor, action, target type, date range), and CSV export. Retention is policy-driven per workspace; older rows are archived to S3 and removed from the live table (covered in the GDPR / residency article).
Why it exists
Three things are true about every audited compliance program:
- The auditor will ask "who did this and when?" for an action that happened months ago.
- The auditor will ask "how do I know nobody tampered with the record?"
- The auditor wants the audit log accessible from their existing SIEM, not from another UI they need separate access to.
The hash chain answers question 2 cryptographically. The SIEM streaming answers question 3. The model + UI answer question 1.
Model
AuditLog fields:
workspace— FK toWorkspace. Audit is workspace-scoped; cross-workspace queries require platform admin.action— string in the format{noun}.{verb}(e.g.change.approve,member.invite,cert.download).target_type— model class name (e.g.ChangeRequest).target_id— UUID of the target row.actor— FK toUserProfile. May be null for system-initiated actions (Celery sweeps, SCIM); in that casemetadata.actor_kindis populated.old_value— JSON snapshot before the change (for updates / deletes).new_value— JSON snapshot after the change (for creates / updates).metadata— JSON dict for action-specific extras (e.g. IP, user agent, SCIM token ID, MFA recency state).created_at— server timestamp.prev_hash— SHA-256 of the immediately preceding row in this workspace.hash— SHA-256 over(prev_hash, action, target_type, target_id, actor_id, old_value, new_value, metadata, created_at).
The first row in a workspace's chain uses a workspace-specific genesis hash (sha256("vigilo:" + workspace_id)) as its prev_hash.
AuditLog.log() helper
The recommended writer is AuditLog.log(...):
AuditLog.log(
workspace=request.workspace,
actor=request.user.profile,
action="change.approve",
target=change,
old_value=change_serializer_before,
new_value=change_serializer_after,
metadata={"ip": request.META.get("REMOTE_ADDR")},
)
The helper computes prev_hash and hash inside a database transaction with SELECT ... FOR UPDATE on the workspace's latest audit row, so concurrent writers produce a deterministic chain rather than racing (T3.5 hardens this). The hash is committed atomically with the row; a partial write that leaves a row without a hash is impossible.
verify_chain() helper
AuditLog.verify_chain(workspace, since=None) walks the chain from oldest to newest (or from since forward), recomputing each hash and comparing to the stored value. Returns (ok: bool, first_break: AuditLog | None, break_reason: str | None).
A break can happen if:
- A row was deleted (the next row's
prev_hashno longer matches). - A row's content was modified (its
hashno longer matches the recomputed value). - A row's
prev_hashwas tampered with. - Two rows were inserted with the same
prev_hash(the chain forked).
The CLI runs the same check: vigilo audit verify (see the plugins + CLI article).
Audit retention (W0.8)
Per-workspace retention policy in Workspace.settings['audit_retention'] (covered in the GDPR + residency article). Old rows are archived to S3 by the vigilo.audit.archive_sweep nightly task and removed from the live table. The archive object includes the row's prev_hash and hash, so an offline verification can stitch archived rows back into the chain.
SIEM streaming (WC.5)
Every new audit row is dispatched to any configured external sink via the vigilo.audit.stream Celery task. Targets are configured per workspace under Settings → SIEM:
- cloudwatch — AWS CloudWatch Logs. Config: log group, log stream, AWS region, AWS creds (encrypted).
- elasticsearch — generic Elasticsearch / OpenSearch. Config: URL, index name, basic auth or bearer token.
- splunk — Splunk HEC. Config: HEC URL, HEC token, source type.
- syslog — UDP or TCP syslog. Config: host, port, facility, severity.
- webhook — generic HTTPS POST. Config: URL, HMAC secret (encrypted, signs the payload).
Each target has is_active, last_success_at, last_failure_at, failure_count. Failed deliveries retry with exponential backoff up to 6 hours; after that the target is marked degraded and an in-app warning fires for owners.
Streamed rows include the full audit record plus a vigilo_meta envelope with workspace slug, hash, prev_hash, and a Vigilo trace ID so SIEM-side dedup is possible.
Human-readable diff UI (T2.6)
The audit detail panel shows the diff between old_value and new_value as a side-by-side or unified view (toggleable). Field renames, type changes, list adds/removes are highlighted in green/red. Sensitive fields (encrypted tokens, MFA secrets) are NEVER serialized into old_value / new_value — the diff shows [encrypted] instead.
The diff renderer is at frontend/src/help/.../AuditDiff.tsx and uses the same components as the change-detail diff, so the visual feels consistent with the rest of the product.
Common workflows
1. Investigate "who approved this change?"
- Open the change. The activity timeline shows the approval entry with the approver's name.
- Click View audit entry on the timeline row.
- The audit panel shows the full record, including IP, user agent, and MFA recency at decision time.
2. Verify integrity for an auditor
- Open Governance → Audit → Verify integrity (admin / owner only).
- The action runs
verify_chainfor the entire workspace. Result is shown inline: green check + first row hash + last row hash, or red banner with the breaking row ID. - Export the integrity report PDF for the auditor's working papers.
3. Stream to Splunk
- Settings → SIEM → New target → Splunk.
- Paste HEC URL and token. Pick source type (default
vigilo:audit). - Save and click Test. The test sends one synthetic row; check Splunk to confirm.
- Once verified, every new audit row arrives in Splunk within seconds.
4. Search audit log for an investigation
- Governance → Audit.
- Filter chip Action:
cert.download. Filter chip Date range: last 30 days. - The list shows every cert download in that period with actor, IP, and the cert that was downloaded.
- Export CSV for the investigation file.
Permissions
| Action | Role |
|---|---|
| View audit list | Viewer or higher |
| View audit detail (including diff) | Engineer or higher |
| Verify integrity | Admin / Owner |
| Configure SIEM targets | Owner |
| Export CSV | Admin / Owner |
The actor field is always shown — there is no anonymized audit view. If a user has been right-to-be-forgotten'd (see GDPR article), the actor renders as [Redacted User] but the FK is preserved.
Troubleshooting
verify_chain reports a break. This is a security event. Capture the break row ID, preserve the database backup from before the break, and contact the platform security team. The most common (non-malicious) cause is a botched DBA-side cleanup; the more concerning cause is intrusion.
SIEM target shows last_failure_at recently. Check the failure reason in the target's row menu. Common causes: HEC token rotated, Splunk source type not configured, ES index doesn't exist.
Audit rows for a Celery sweep have no actor. Expected — system-initiated actions have actor=NULL and metadata.actor_kind='celery:task_name'. The UI shows "System (task_name)" in the actor column.
The diff shows [encrypted] for a field I'd like to inspect. By design. Use the field-specific tooling (e.g. Integrations → Slack → Test connection) to verify the value is correct without exposing it.
The audit page is slow for very old date ranges. Rows beyond retention_days are archived to S3 and not in the live table. The page indicates this with a banner; query the archive directly for older periods.
Related articles
- Compliance frameworks — controls reference audit evidence.
- Evidence collection — audit-derived evidence queries.
- RBAC and tenancy — what the audit log is recording the permission checks against.
- GDPR and residency — retention + archival policy.