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

These reports collect evidence an auditor will ask for. They do not evaluate compliance — no report computes a requirement status or a pass/fail verdict — and they only contain what Sentinel has recorded. Read each report's 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.

SectionRecorded byRequires
GDPR user_data_access, SOC 2 total_usersUser activity, one entry per authenticated requestConfig.UserExtractor (v2.3.0+)
GDPR data_deletionsDELETE audit entries from the GORM pluginPass your *gorm.DB to Mount
GDPR data_exportsREAD audit entriesYour application writing them — the GORM plugin records CREATE, UPDATE, and DELETE only
GDPR unusual_access, SOC 2 anomaly_eventsAnomaly detectionAnomaly.Enabled and UserExtractor
PCI-DSS auth_eventsLogin audit entries (Resource: "auth") — logins AuthShield observes on your app, and dashboard loginsAuthShield.Enabled with a LoginRoute for app logins (v2.3.0+)
PCI-DSS incidents and blocked threats, SOC 2 monitoring evidenceThreat events from the WAF, rate limiter, AuthShield, and anomaly detectionThe WAF or other detectors enabled
SOC 2 incident_responseThreats marked resolved in the dashboardOperators triaging threats
SOC 2 access_control.audit_logsEvery 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:

FieldMeaning
storage_driversqlite, postgres, or memory.
durabletrue only for SQLite and Postgres — data that survives a restart.
retention_days, audit_retention_daysHow long threat/activity records and audit entries are kept before deletion.
oldest_threat_event, oldest_audit_entryThe earliest records in storage.
warningsEvery 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

With 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

ReportEndpointTime Window
GDPRGET /sentinel/api/reports/gdpr?window=, default 720h
PCI-DSSGET /sentinel/api/reports/pci-dssFixed 90 days
SOC 2GET /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.

FieldContents
user_data_accessPer user: user_id, routes_accessed (route patterns), access_count, last_access.
data_exportsREAD audit entries.
data_deletionsDELETE audit entries, with the deleted record's before-state.
unusual_accessAnomalyDetected threat events.
summarytotal_users, total_data_accesses, total_exports, total_deletions, unusual_access_count.
GDPR Response (abridged)json
{
"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.

FieldContents
auth_eventstotal_attempts, success_count, failure_count, failure_rate (percent), from login audit entries.
security_incidentsEvery threat event in the window, newest first.
blocked_threatsThreat events Sentinel blocked.
summarytotal_incidents, critical_incidents, high_incidents, blocked_count, unique_attacker_ips.

Audit retention

PCI-DSS 10.5.1 requires 12 months of audit history. 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.

FieldContents
monitoring_evidencetotal_events_processed (threat events recorded in the window), threat_stats (counts by severity, blocked, unique IPs, top attack types), security_score.
incident_responseThreat events marked resolved.
access_controltotal_users, audit_logs (every audit entry in the window, including dashboard actions and logins), blocked_ips.
anomaly_eventsAnomalyDetected threat events.
summarytotal_threats_detected, total_threats_blocked, total_anomalies, total_audit_entries, active_blocked_ips.

Fixed in v2.2.2

Before v2.2.2 the PCI-DSS 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.json
curl -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.

report.gogo
1gen := reports.NewGenerator(store)
2gen.SetSourceInfo(reports.SourceInfo{
3 StorageDriver: "postgres",
4 RetentionDays: 90,
5 AuditRetentionDays: 365,
6 UserActivityRecorded: true, // Config.UserExtractor is set
7})
8
9report, err := gen.GenerateSOC2(ctx, 30*24*time.Hour)
10if err != nil {
11 return err
12}
13for _, w := range report.Provenance.Warnings {
14 log.Println("report warning:", w)
15}

Next Steps


Built with by JB