SOC 2 Audit Logging for Sensitive PII Read Access
SOC 2 Audit Logging for Sensitive PII Read Access
Compliance reference: SOC 2 Type II — CC6.1 (Logical Access Controls), CC6.3 (Access Monitoring)
Introduced in: v0.1.127
Overview
All endpoints that return sensitive personally identifiable information (PII) now write a structured entry to the immutable audit log at the moment of access. This satisfies the SOC 2 requirement to log all access to sensitive data — not just mutations.
Two new audit actions are emitted:
person.accessed— an individual screening record (includingdateOfBirth,nationality,registrationNumber) was readexport.accessed— a bulk export of screening data, screening history, or the audit trail was downloaded
What is Logged
Every audit entry captures:
| Field | Description |
|---|---|
userId | Authenticated user who performed the read |
action | person.accessed or export.accessed |
resourceType | e.g. person, people_export, screening_history_export, audit_trail_export |
resourceId | Record ID (individual reads) or user ID (bulk exports) |
ipAddress | Extracted from x-forwarded-for, x-real-ip, or the direct connection |
userAgent | Browser or API client user-agent string |
metadata | Contextual detail — see below |
Metadata by Action
person.accessed (single record read)
{
"fullName": "Jane Smith",
"fieldsAccessed": ["dateOfBirth", "nationality", "registrationNumber"],
"matchCount": 2
}
person.accessed via the external REST API v1 additionally includes:
{
"via": "rest_api_v1"
}
export.accessed — people list
{
"format": "csv",
"recordCount": 150,
"exportType": "people_list",
"fieldsExported": ["fullName", "dateOfBirth", "nationality", "registrationNumber", "status"]
}
export.accessed — screening history
{
"format": "csv",
"peopleCount": 150,
"matchCount": 47,
"reviewCount": 23,
"exportType": "screening_history",
"fieldsExported": ["personName", "sanctionedEntityName", "confidenceScore", "matchAlgorithm", "reviewDecisions"]
}
export.accessed — audit trail
{
"format": "csv",
"recordCount": 5000,
"exportType": "audit_trail"
}
Dual-Log Pattern for Export Endpoints
The three bulk export endpoints emit two audit entries per request:
export.accessed— SOC 2 CC6.1/CC6.3 requirement (new in v0.1.127)gdpr.data_exported— GDPR Article 20 portability requirement (pre-existing)
Both entries are preserved. No existing audit entries were removed or modified.
Covered Endpoints
| Endpoint | Audit Action | Access Path |
|---|---|---|
GET /api/people/[id] | person.accessed | Internal UI |
GET /api/v1/people/[id] | person.accessed | External REST API v1 |
GET /api/export/people | export.accessed | Internal UI |
GET /api/export/screening-history | export.accessed | Internal UI |
GET /api/export/audit-trail | export.accessed | Internal UI |
Resilience
Audit log writes are designed to be non-blocking. If the database is unavailable when writing an audit entry, the error is suppressed and the primary API response is returned normally. This ensures audit failures never interrupt user-facing functionality, while the platform monitoring layer can separately alert on audit write failures.
Viewing Audit Logs
All person.accessed and export.accessed entries appear in the audit trail alongside other system events. Use the audit trail export (GET /api/export/audit-trail) or the monitoring dashboard to query and filter by action type for compliance reporting.