> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ferrum.thalamiq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Audit Logs

> How Ferrum records, stores, and exposes FHIR AuditEvent logs.

Ferrum records an **authoritative audit trail** of security-relevant activity performed through
the FHIR API, modeled as FHIR [`AuditEvent`](https://hl7.org/fhir/auditevent.html) resources.

## Key properties

| Property      | Behavior                                                               |
| ------------- | ---------------------------------------------------------------------- |
| Storage       | Separate `audit_log` table (not in `resources`)                        |
| Mutability    | Append-only and immutable (DB triggers prevent UPDATE/DELETE)          |
| Reliability   | Best-effort: audit failures must not fail the main request             |
| Throughput    | Async write path (queue + background insert)                           |
| Output format | Full serialized FHIR `AuditEvent` (R4/R5 depending on `fhir.version`)  |
| Access        | Only via internal admin endpoints (not queryable via normal FHIR APIs) |

## What gets audited

Ferrum audits **FHIR REST interactions** under `/fhir` including (when enabled):

* `read`, `vread`, `history`
* `search`
* `create`, `update`, `patch`, `delete`
* `capabilities` (`GET /fhir/metadata`)
* `$operation` and `$export`
* `batch` / `transaction`

<Note>
  Audit logging currently targets the FHIR API surface. Internal routes like `/admin` and `/health`
  are not audited by the same middleware.
</Note>

## Captured security context (SMART / OAuth2)

When requests are authenticated, Ferrum binds the audit record to the SMART security context:

* `client_id` (OAuth2 client)
* `user_id` (end-user subject when present)
* `scopes` (granted scopes)
* `token_type` inferred as `user` / `system` / `anonymous` / `unknown`

This context is stored both as indexed columns (for fast filtering) and inside the JSON `details`.

## Captured data access semantics

For each interaction, Ferrum captures (best-effort):

* **Target resource**: `resource_type` + `resource_id` when known
* **Patient subject**: `patient_id` when resolvable
* **Request correlation**: `request_id` (server-generated), client IP, user agent
* **Outcome**:
  * `success` for HTTP `< 400`
  * `authz_failure` for `401/403`
  * `processing_failure` for other `>= 400` responses

### Search events and `entity.query`

FHIR recommends that servers capture search activity as an **Execute** event and include the raw
HTTP request as base64 in `AuditEvent.entity.query`. Ferrum can do this for search requests:

* Stores the full HTTP start line + headers + body as base64binary
* Also records the “harmonized” request URL string (path + query) in `details`

### OperationOutcome capture

On failures, Ferrum can capture the returned `OperationOutcome` and include it in the serialized
AuditEvent (contained + linked from `entity.what`).

## Patient resolution

When possible, audit records include the patient subject:

* From SMART context (`patient` claim on the token, if present)
* From a compartment path (`/fhir/Patient/{id}/...`)
* From direct resource targets (`Patient/{id}`)
* For successful searches: by inspecting a `searchset` Bundle and extracting referenced patients

<Note>
  For searches that touch multiple patients, Ferrum can emit **one audit record per patient** to
  improve privacy segmentation. This is configurable.
</Note>

## Storage model (`audit_log`)

Audit records are stored in PostgreSQL in the `audit_log` table.

* Structured columns for common filters (action, outcome, patient\_id, request\_id, …)
* `audit_event` column stores the complete FHIR AuditEvent as `JSONB`
* `details` stores additional structured metadata (SMART + HTTP context)

The table is created in the main DB migration (`server/migrations/001_init.sql`) and is protected
with triggers to prevent modification.

## Admin API

Audit logs are intentionally not accessible via the normal FHIR resource API. Instead, Ferrum
exposes an internal admin interface (behind admin auth middleware).

### List audit events

`GET /admin/audit/events`

Query parameters:

* `action`: interaction (e.g. `read`, `search`, `create`, `transaction`)
* `outcome`: `success` | `authz_failure` | `processing_failure`
* `resourceType`, `resourceId`, `patientId`, `clientId`, `userId`, `requestId`
* `limit` (default `100`, max `1000`), `offset`

Example:

```bash theme={null}
curl "http://localhost:8080/admin/audit/events?outcome=authz_failure&limit=25"
```

### Get a single audit event

`GET /admin/audit/events/{id}`

Returns the full stored row including the serialized `audit_event` JSON.

```bash theme={null}
curl "http://localhost:8080/admin/audit/events/123"
```

## Configuration

Audit logging is configured under `logging.audit`:

```yaml theme={null}
logging:
  audit:
    enabled: true

    # Outcome filters
    include_success: true
    include_authz_failure: true
    include_processing_failure: true

    # Interaction filters
    interactions:
      read: true
      vread: true
      history: true
      search: true
      create: true
      update: true
      patch: true
      delete: true
      capabilities: true
      operation: true
      batch: true
      transaction: true
      export: true

    # Payload capture controls
    capture_search_query: true
    capture_operation_outcome: true
    per_patient_events_for_search: true
```

Common hardening examples:

```yaml theme={null}
# Only keep failures (common in high-volume deployments)
logging:
  audit:
    include_success: false

# Avoid storing raw HTTP bodies for searches
logging:
  audit:
    capture_search_query: false
```

## Operational notes

* Audit writes are queued and inserted asynchronously; on DB errors, the server logs warnings and
  continues serving requests.
* The audit log table can grow quickly. Use standard Postgres operational strategies (partitioning,
  retention policies, backups) depending on your compliance needs.

## Related docs

* [Security](/server/security)
* [Authentication](/api-reference/authentication)
* [CRUD Operations](/server/crud-operations)
