Audit, Security & Data Retention
Audit Loggingโ
Files: src/audit/audit-logger.ts, src/audit/audit-routes.ts
Every patient-related action in ChartChat is persisted as a FHIR AuditEvent resource. This is the HIPAA-standard mechanism for tamper-evident, append-only activity logs. Audit events are never deleted by the system (see Data Retention below).
AuditEvent field mappingโ
| AuditEvent field | Value |
|---|---|
recorded | Timestamp of the event |
agent[0].who | User reference (Patient/abc or Practitioner/xyz) |
agent[0].extension[userRole] | patient, admin, support, or system |
agent[0].extension[source] | web, mobile, or api |
agent[0].extension[userAgent] | Browser/client user-agent string |
agent[0].network.address | Client IP address |
entity[0] (role=1) | Patient reference |
entity[1] (role=2) | The FHIR resource accessed/modified |
entity[1].detail | beforeValue, afterValue, fileName, metadata |
subtype[0].code | ChartChat action code (see table below) |
action | FHIR CRUDE code (C, R, U, D, E) |
outcome | 0 = SUCCESS, 8 = FAILURE |
Action codesโ
All action codes use the system https://chartchat.com/audit/action.
Patient Data Accessโ
| Code | FHIR action | Description |
|---|---|---|
VIEW_PATIENT_PROFILE | R | Patient profile page viewed |
VIEW_OBSERVATION | R | Lab result viewed |
VIEW_DIAGNOSTIC_REPORT | R | Diagnostic report viewed |
VIEW_ENCOUNTER | R | Encounter/visit viewed |
VIEW_APPOINTMENT | R | Appointment viewed |
VIEW_MEDICATIONS | R | Medications page viewed |
VIEW_CONDITIONS | R | Conditions page viewed |
VIEW_ALLERGY | R | Allergy record viewed |
VIEW_IMMUNIZATIONS | R | Immunizations viewed |
VIEW_PROCEDURES | R | Procedures viewed |
Documents & Filesโ
| Code | FHIR action | Description |
|---|---|---|
DOWNLOAD_REPORT | E | Report/document downloaded |
VIEW_DOCUMENT | R | Document reference viewed |
UPLOAD_DOCUMENT | C | Document uploaded |
GENERATE_PDF | E | PDF generated |
Data Modificationโ
| Code | FHIR action | Description |
|---|---|---|
UPDATE_PATIENT_PROFILE | U | Patient profile updated |
CREATE_RECORD | C | New FHIR resource created |
DELETE_RECORD | D | FHIR resource deleted |
Authentication & Sessionโ
| Code | FHIR action | Description |
|---|---|---|
LOGIN_SUCCESS | E | Successful login |
LOGIN_FAILED | E | Failed login attempt |
LOGOUT | E | User logged out |
SESSION_EXPIRED | E | Session timed out |
TOKEN_REFRESH | E | OAuth token refreshed |
PASSWORD_CHANGED | E | Password changed |
EHR Integrationโ
| Code | FHIR action | Description |
|---|---|---|
EHR_CONNECT | E | EHR account connected |
EHR_SYNC | E | EHR data sync completed |
EHR_SYNC_FAILED | E | EHR sync failed |
EHR_DISCONNECT | E | EHR account disconnected |
Admin & Exportโ
| Code | FHIR action | Description |
|---|---|---|
ADMIN_EXPORT_DATA | E | Admin exported patient data |
ADMIN_DELETE_PATIENT | D | Admin deleted a patient |
SUPPORT_ACCESS_GRANTED | E | Support access granted |
EXPORT_PATIENT_DATA | E | Patient data exported |
GENERATE_REPORT | E | Report generated |
Security & AIโ
| Code | FHIR action | Description |
|---|---|---|
SUSPICIOUS_LOGIN_ATTEMPT | E | Unusual login detected |
UNUSUAL_DATA_ACCESS | E | Anomalous access flagged |
RATE_LIMIT_EXCEEDED | E | Rate limit hit |
CHAT_MESSAGE_SENT | E | AI chat message sent |
CHAT_SAFETY_TRIGGERED | E | AI safety refusal |
System / Admin Configโ
| Code | FHIR action | Description |
|---|---|---|
ROLE_UPDATED | E | User role changed |
ACCESS_POLICY_CHANGED | E | Access policy modified |
CONFIG_UPDATED | E | System configuration changed |
Audit API Endpointsโ
POST /api/audit/log โ Client-side event loggingโ
Called by the Member Portal frontend to record client-side events (page views, downloads, AI chat actions). The server automatically extracts the user identity, role, IP, and user-agent from the request.
Auth: Patient or admin bearer token required.
Request body:
{
"action": "VIEW_OBSERVATION",
"resourceType": "Observation",
"resourceId": "abc123",
"status": "SUCCESS",
"metadata": { "category": "lab" }
}
| Field | Required | Description |
|---|---|---|
action | Yes | One of the action codes above |
resourceType | No | FHIR resource type being accessed |
resourceId | No | FHIR resource ID |
status | No | SUCCESS or FAILURE. Default: SUCCESS |
metadata | No | Free-form JSON object |
beforeValue | No | Previous value (for update/delete events) |
afterValue | No | New value (for create/update events) |
fileName | No | File name (for document events) |
reason | No | Reason for access (admin/support use) |
GET /api/audit/export โ CSV exportโ
Admin-only. Exports AuditEvent records as a CSV file with optional filters. Returns up to 5,000 records.
Auth: Admin (Practitioner) bearer token required. Patients are blocked.
Query parameters:
| Parameter | Description |
|---|---|
from | ISO date โ events on or after this date |
to | ISO date โ events on or before this date |
patient | Filter by patient reference (e.g., Patient/abc123) |
action | Filter by action code (e.g., VIEW_OBSERVATION) |
status | SUCCESS or FAILURE |
Response: text/csv file downloaded as audit-log-YYYY-MM-DD.csv
CSV columns: timestamp, userId, userRole, patientId, action, resourceType, resourceId, status, ipAddress, source, userAgent, outcome
Standard FHIR audit queryโ
For paginated, filtered queries the portals call the standard FHIR endpoint directly:
GET /fhir/R4/AuditEvent?patient=Patient/abc123&date=ge2024-01-01&_sort=-date
Breach Detectionโ
File: src/security/breachDetection.ts
The breach detection system monitors every PHI access and fires an alert if a single user accesses an unusual number of patient records in a short time window.
Thresholdsโ
| Window | Threshold | Description |
|---|---|---|
| 1 hour | 100 accesses | More than 100 PHI reads in a single hour |
| 24 hours | 500 accesses | More than 500 PHI reads in a single day |
Counters are maintained in Redis using atomic INCR with a TTL matching the window (3,600s for hourly, 86,400s for daily).
Alert behaviorโ
When a threshold is exceeded:
- Checks Redis for an alert cooldown key (
breach:alert:{userId}) โ if set, suppresses the duplicate alert. - Sets the cooldown key with a 1-hour TTL to prevent alert flooding.
- Logs a warning to the server log with
userId,hourCount, anddayCount. - Sends an email alert to
config.supportEmailwith the user ID, reason, timestamp, and recommended HIPAA incident response steps.
Redis key patternsโ
| Key | TTL | Value |
|---|---|---|
breach:h:{userId}:{hourBucket} | 3,600s | Count of PHI accesses this hour |
breach:d:{userId}:{dayBucket} | 86,400s | Count of PHI accesses today |
breach:alert:{userId} | 3,600s | Alert suppression flag |
Where it is triggeredโ
trackPhiAccess(userId) is called from the PHI audit middleware (fhir/phi-audit-middleware.ts) on every FHIR read operation. It is fire-and-forget โ errors are silently swallowed to ensure breach detection never disrupts normal request handling.
Data Retentionโ
File: src/workers/dataRetention.ts
A daily background job enforces data retention policies per HIPAA requirements. It runs at 02:00 every day via BullMQ.
Retention rulesโ
| Data type | Policy | Reason |
|---|---|---|
AuditEvent | Keep for 7 years โ never deleted automatically | HIPAA ยง 164.530(j) requires 6-year minimum; ChartChat keeps 7 for margin |
Revoked Login records | Purge after 90 days | Short-term forensics window; no PHI in login records |
| Soft-deleted non-PHI resources | Purge after 30 days | Clean up deleted resources |
AuditEvent resources are never automatically deleted by the data retention job. The job only logs a reminder to the server log when events reach the 7-year boundary, so operators know when to archive or review them per their own incident response policy.
If you need to delete audit events, use the Admin Portal โ Super Admin โ Purge tool, which requires an explicit date cutoff and admin confirmation.
Job detailsโ
- Queue:
DataRetentionQueue - Schedule: Daily at 02:00 (BullMQ cron:
0 2 * * *) - Job ID:
data-retention-daily(deduped โ only one instance runs at a time) - Processes up to 500 revoked login records per run