All Docs
FeaturesCalmony Sanctions MonitorUpdated March 12, 2026

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 (including dateOfBirth, nationality, registrationNumber) was read
  • export.accessed — a bulk export of screening data, screening history, or the audit trail was downloaded

What is Logged

Every audit entry captures:

FieldDescription
userIdAuthenticated user who performed the read
actionperson.accessed or export.accessed
resourceTypee.g. person, people_export, screening_history_export, audit_trail_export
resourceIdRecord ID (individual reads) or user ID (bulk exports)
ipAddressExtracted from x-forwarded-for, x-real-ip, or the direct connection
userAgentBrowser or API client user-agent string
metadataContextual 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:

  1. export.accessed — SOC 2 CC6.1/CC6.3 requirement (new in v0.1.127)
  2. gdpr.data_exported — GDPR Article 20 portability requirement (pre-existing)

Both entries are preserved. No existing audit entries were removed or modified.

Covered Endpoints

EndpointAudit ActionAccess Path
GET /api/people/[id]person.accessedInternal UI
GET /api/v1/people/[id]person.accessedExternal REST API v1
GET /api/export/peopleexport.accessedInternal UI
GET /api/export/screening-historyexport.accessedInternal UI
GET /api/export/audit-trailexport.accessedInternal 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.