Compliance Reports
Sentinel generates GDPR, PCI-DSS, and SOC 2 reports from the security data it records: threat events, audit logs, user activity, and IP blocks. Reports are generated on demand through the API or the dashboard and returned as JSON.
Evidence, not an assessment
provenance block before relying on it.Where the Data Comes From
A report section can only be as complete as the component that feeds it. If a feeder isn't configured, its section is empty — and an empty section means no data, not a clean record.
| Section | Recorded by | Requires |
|---|---|---|
GDPR user_data_access, SOC 2 total_users | User activity, one entry per authenticated request | Config.UserExtractor (v2.3.0+) |
GDPR data_deletions | DELETE audit entries from the GORM plugin | Pass your *gorm.DB to Mount |
GDPR data_exports | READ audit entries | Your application writing them — the GORM plugin records CREATE, UPDATE, and DELETE only |
GDPR unusual_access, SOC 2 anomaly_events | Anomaly detection | Anomaly.Enabled and UserExtractor |
PCI-DSS auth_events | Login audit entries (Resource: "auth") — logins AuthShield observes on your app, and dashboard logins | AuthShield.Enabled with a LoginRoute for app logins (v2.3.0+) |
| PCI-DSS incidents and blocked threats, SOC 2 monitoring evidence | Threat events from the WAF, rate limiter, AuthShield, and anomaly detection | The WAF or other detectors enabled |
SOC 2 incident_response | Threats marked resolved in the dashboard | Operators triaging threats |
SOC 2 access_control.audit_logs | Every audit entry, including dashboard actions (v2.3.0+) | — |
Provenance and Truncation
Every report carries a provenance block (v2.3.0+) describing the data behind it:
| Field | Meaning |
|---|---|
storage_driver | sqlite, postgres, or memory. |
durable | true only for SQLite and Postgres — data that survives a restart. |
retention_days, audit_retention_days | How long threat/activity records and audit entries are kept before deletion. |
oldest_threat_event, oldest_audit_entry | The earliest records in storage. |
warnings | Every reason the data may not support the report: in-memory storage, a window longer than retention, a window that starts before the oldest stored record, an empty store, audit retention under PCI-DSS's 12 months, no UserExtractor, no source of READ entries. |
List sections are capped (500 to 5,000 rows depending on the section). When a list hits its cap, its name appears in the report's truncated array. Summary counts come from aggregate queries and stay exact either way.
In-memory storage is refused in release mode
Storage.Driver: sentinel.Memory in gin.ReleaseMode, the report endpoints return 409 with code EPHEMERAL_STORAGE: that data is lost on every restart, so a report would cover only the time since the last deploy while reading as a complete record. Pass ?acknowledge_ephemeral=true to generate it anyway.Available Reports
| Report | Endpoint | Time Window |
|---|---|---|
| GDPR | GET /sentinel/api/reports/gdpr | ?window=, default 720h |
| PCI-DSS | GET /sentinel/api/reports/pci-dss | Fixed 90 days |
| SOC 2 | GET /sentinel/api/reports/soc2 | ?window=, default 720h |
Every endpoint requires a dashboard token and wraps the report in a { "data": ... } envelope. window takes any Go duration (168h, 336h, 2160h); an unparseable value falls back to 720h.
GDPR Report
How user data was accessed, exported, and deleted in the window, plus anomalous access.
| Field | Contents |
|---|---|
user_data_access | Per user: user_id, routes_accessed (route patterns), access_count, last_access. |
data_exports | READ audit entries. |
data_deletions | DELETE audit entries, with the deleted record's before-state. |
unusual_access | AnomalyDetected threat events. |
summary | total_users, total_data_accesses, total_exports, total_deletions, unusual_access_count. |
{"data": {"generated_at": "2026-09-11T10:00:00Z","window_start": "2026-08-12T10:00:00Z","window_end": "2026-09-11T10:00:00Z","user_data_access": [{"user_id": "user-abc123","routes_accessed": ["/api/profile", "/api/orders/:id"],"access_count": 87,"last_access": "2026-09-11T09:45:00Z"}],"data_exports": [],"data_deletions": [ { "action": "DELETE", "resource": "customers", "resource_id": "412", "...": "..." } ],"unusual_access": [],"summary": {"total_users": 42,"total_data_accesses": 1580,"total_exports": 0,"total_deletions": 3,"unusual_access_count": 0},"provenance": {"storage_driver": "sqlite","durable": true,"retention_days": 90,"audit_retention_days": 365,"oldest_threat_event": "2026-06-14T08:02:11Z","oldest_audit_entry": "2026-06-14T08:05:40Z","warnings": ["data_exports lists READ audit entries, which Sentinel's GORM plugin does not record (it records CREATE, UPDATE, and DELETE): the section stays empty unless your application writes READ entries itself."]}}}
PCI-DSS Report
Authentication activity, security incidents, and blocked threats over the last 90 days.
| Field | Contents |
|---|---|
auth_events | total_attempts, success_count, failure_count, failure_rate (percent), from login audit entries. |
security_incidents | Every threat event in the window, newest first. |
blocked_threats | Threat events Sentinel blocked. |
summary | total_incidents, critical_incidents, high_incidents, blocked_count, unique_attacker_ips. |
Audit retention
Storage.AuditRetentionDays defaults to 365; set lower and the report's provenance says so.SOC 2 Report
Monitoring, incident-response, and access-control evidence for the window.
| Field | Contents |
|---|---|
monitoring_evidence | total_events_processed (threat events recorded in the window), threat_stats (counts by severity, blocked, unique IPs, top attack types), security_score. |
incident_response | Threat events marked resolved. |
access_control | total_users, audit_logs (every audit entry in the window, including dashboard actions and logins), blocked_ips. |
anomaly_events | AnomalyDetected threat events. |
summary | total_threats_detected, total_threats_blocked, total_anomalies, total_audit_entries, active_blocked_ips. |
Fixed in v2.2.2
blocked_threats list filtered on the wrong field, the SOC 2 blocked count could never exceed 1, and total_events_processed was a meaningless sum. Reports generated by older versions should not be relied on.Dashboard and JSON Export
The dashboard's Reports page lets you pick a report type and window, generate it, and export it with the Export JSON button as sentinel-<type>-report-<date>.json. From the API, extract the .data field:
TOKEN=$(curl -s -X POST http://localhost:8080/sentinel/api/auth/login \-H "Content-Type: application/json" \-d '{"username":"admin","password":"<your-password>"}' | jq -r .token)curl -s -H "Authorization: Bearer $TOKEN" \"http://localhost:8080/sentinel/api/reports/gdpr?window=720h" | jq '.data' > gdpr-report.jsoncurl -s -H "Authorization: Bearer $TOKEN" \"http://localhost:8080/sentinel/api/reports/pci-dss" | jq '.data.provenance'
Generating Reports in Code
Reports come from reports.Generator, which queries any storage.Store. Tell it about the store with SetSourceInfo so the provenance block can describe it; without that, reports warn that durability and retention are unknown.
1gen := reports.NewGenerator(store)2gen.SetSourceInfo(reports.SourceInfo{3 StorageDriver: "postgres",4 RetentionDays: 90,5 AuditRetentionDays: 365,6 UserActivityRecorded: true, // Config.UserExtractor is set7})89report, err := gen.GenerateSOC2(ctx, 30*24*time.Hour)10if err != nil {11 return err12}13for _, w := range report.Provenance.Warnings {14 log.Println("report warning:", w)15}
Next Steps
- User Extractor -- Record the user activity the GDPR report needs
- Audit Logging -- The audit entries behind exports, deletions, and logins
- Anomaly Detection -- Powers unusual access (GDPR) and anomaly events (SOC 2)
- Auth Shield -- Records the login attempts in PCI-DSS auth events
- Security Score -- Included in SOC 2 monitoring evidence