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

# Security

> Review Matcher's defense-in-depth security: authentication, RBAC, tenant isolation, encryption, and audit logging across every layer.

Matcher implements comprehensive security controls to protect financial reconciliation data. This guide covers authentication, authorization, tenant isolation, encryption, and compliance features.

Financial reconciliation data is among the most sensitive in any organization — it touches transaction records, counterparty information, and regulatory obligations. Regulations such as SOC 2, PCI DSS, and local financial authority requirements mandate specific controls around access, encryption, and auditability. Matcher is designed with these requirements in mind, providing defense-in-depth security across every layer of the stack.

## Overview

***

Matcher's security architecture is built on several layers. Every API request passes through multiple security checkpoints before reaching your data.

<Steps>
  <Step>
    First, TLS encrypts the connection.
  </Step>

  <Step>
    Then authentication verifies who you are:

    * Tenant isolation ensures you only see your own data.
    * RBAC checks whether you're allowed to perform the action.
  </Step>

  <Step>
    Finally, the system logs everything for audit purposes.
  </Step>
</Steps>

### Layer protection

| Layer            | Protection                   |
| ---------------- | ---------------------------- |
| Transport        | TLS 1.2+ encryption          |
| Authentication   | JWT tokens via lib-auth      |
| Tenant Isolation | Schema-per-tenant PostgreSQL |
| Authorization    | Role-based access control    |
| Audit            | Immutable append-only logs   |
| Storage          | Encryption at rest           |

## Authentication

***

Matcher uses the shared `lib-auth` library (v2) for authentication, supporting JWT-based access control with HMAC signing.

### Configuration

Three environment variables control authentication:

| Variable               | Required          | Description                                         |
| ---------------------- | ----------------- | --------------------------------------------------- |
| `AUTH_ENABLED`         | Yes               | Enable or disable authentication (`true`/`false`)   |
| `AUTH_SERVICE_ADDRESS` | When auth enabled | Address of the external authorization service       |
| `AUTH_JWT_SECRET`      | When auth enabled | Shared secret for HMAC token signature verification |

When `AUTH_ENABLED=false` (development only), Matcher uses a default tenant ID (`11111111-1111-1111-1111-111111111111`) and skips authorization checks.

### JWT token structure

Matcher validates JWTs signed with HMAC algorithms (HS256, HS384, HS512). The token must include tenant identification claims:

```json theme={null}
{
  "sub": "user_123",
  "tenant_id": "11111111-1111-1111-1111-111111111111",
  "tenant_slug": "acme-corp",
  "iat": 1705749600,
  "exp": 1705753200,
  "nbf": 1705749600
}
```

| Claim                         | Required | Description                                    |
| ----------------------------- | -------- | ---------------------------------------------- |
| `tenant_id` or `tenantId`     | Yes      | Tenant UUID for schema isolation               |
| `tenant_slug` or `tenantSlug` | No       | Human-readable tenant identifier               |
| `sub`                         | No       | User ID (used for audit logging)               |
| `exp`                         | Yes      | Token expiration time                          |
| `nbf`                         | No       | Not-before time (token is invalid before this) |

### Required headers

All API requests must include authentication:

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/contexts" \
 -H "Authorization: Bearer $JWT_TOKEN"
```

### Token validation

Matcher validates tokens on every request:

1. **Signature verification**: Validates HMAC signature using the configured shared secret
2. **Expiration check**: Rejects expired tokens (`exp` claim)
3. **Not-before check**: Rejects tokens used before their `nbf` time
4. **Tenant extraction**: Extracts `tenant_id` for schema isolation

## Authorization (RBAC)

***

Role-based access control protects all API endpoints. Matcher delegates authorization to an external auth service via `lib-auth`. Permissions are granular and follow the pattern `resource:sub-resource:action`.

### Permission structure

```
<resource>:<sub-resource>:<action>
```

Examples:

* `configuration:context:create` — Create reconciliation contexts
* `matching:job:run` — Execute matching jobs
* `exception:exception:resolve` — Resolve exceptions

### Complete permission list

Matcher defines six resource domains. Each endpoint requires a specific permission checked against the external authorization service.

#### Configuration

| Permission                          | Description                    |
| ----------------------------------- | ------------------------------ |
| `configuration:context:create`      | Create reconciliation contexts |
| `configuration:context:read`        | View contexts                  |
| `configuration:context:update`      | Update contexts                |
| `configuration:context:delete`      | Delete contexts                |
| `configuration:source:create`       | Create data sources            |
| `configuration:source:read`         | View source configuration      |
| `configuration:source:update`       | Modify source settings         |
| `configuration:source:delete`       | Remove data sources            |
| `configuration:field-map:create`    | Create field mappings          |
| `configuration:field-map:read`      | View field mappings            |
| `configuration:field-map:update`    | Update field mappings          |
| `configuration:field-map:delete`    | Delete field mappings          |
| `configuration:rule:create`         | Create match rules             |
| `configuration:rule:read`           | View match rules               |
| `configuration:rule:update`         | Update match rules             |
| `configuration:rule:delete`         | Delete match rules             |
| `configuration:fee-schedule:create` | Create fee schedules           |
| `configuration:fee-schedule:read`   | View fee schedules             |
| `configuration:fee-schedule:update` | Update fee schedules           |
| `configuration:fee-schedule:delete` | Delete fee schedules           |
| `configuration:schedule:create`     | Create schedules               |
| `configuration:schedule:read`       | View schedules                 |
| `configuration:schedule:update`     | Update schedules               |
| `configuration:schedule:delete`     | Delete schedules               |

#### Ingestion

| Permission                     | Description                              |
| ------------------------------ | ---------------------------------------- |
| `ingestion:import:create`      | Upload transaction files                 |
| `ingestion:job:read`           | View import jobs and transaction details |
| `ingestion:transaction:ignore` | Mark transactions as ignored             |
| `ingestion:transaction:search` | Search transactions                      |

#### Matching

| Permission                   | Description                       |
| ---------------------------- | --------------------------------- |
| `matching:job:run`           | Execute match runs                |
| `matching:job:read`          | View match run results and groups |
| `matching:job:delete`        | Delete match groups (unmatch)     |
| `matching:adjustment:create` | Create adjustment entries         |

#### Exception

| Permission                     | Description                                      |
| ------------------------------ | ------------------------------------------------ |
| `exception:exception:read`     | View exceptions and history                      |
| `exception:exception:resolve`  | Force match or adjust entries                    |
| `exception:exception:dispatch` | Dispatch exceptions to external systems          |
| `exception:callback:process`   | Process external webhook callbacks               |
| `exception:dispute:read`       | View disputes                                    |
| `exception:dispute:write`      | Create disputes, close disputes, submit evidence |
| `exception:comment:write`      | Add or delete comments on exceptions             |

#### Governance

| Permission                        | Description                     |
| --------------------------------- | ------------------------------- |
| `governance:audit:read`           | View audit logs                 |
| `governance:archive:read`         | View and download archives      |
| `governance:actor-mapping:read`   | View actor mappings             |
| `governance:actor-mapping:write`  | Create or update actor mappings |
| `governance:actor-mapping:delete` | Delete actor mappings           |

#### Reporting

| Permission                   | Description                                                     |
| ---------------------------- | --------------------------------------------------------------- |
| `reporting:dashboard:read`   | Access dashboard analytics and metrics                          |
| `reporting:export:read`      | View and export reports (matched, unmatched, summary, variance) |
| `reporting:export-job:write` | Create and cancel export jobs                                   |
| `reporting:export-job:read`  | View export job status and download exports                     |

### Role management

The external authorization service manages roles, not Matcher itself. Configure roles and their associated permissions in your identity provider or auth service. Matcher checks permissions on each request by calling the auth service with the required resource and action.

## Tenant isolation

***

Matcher uses schema-per-tenant isolation in PostgreSQL, providing strong data separation between tenants.

### How it works

When a request arrives, Matcher extracts the tenant ID from the JWT token (never from query parameters or headers you control). It then sets the database connection to use that tenant's schema via `SET LOCAL search_path`, so every query runs in complete isolation.

<Note>
  Even if a bug existed in the application layer, the database enforces separation. Schema isolation is applied per-transaction to prevent connection pool pollution.
</Note>

**Implementation details**

1. **Tenant ID from JWT only**: Never accepted from request parameters
2. **Automatic schema selection**: Applied via `auth.ApplyTenantSchema()`
3. **Per-transaction isolation**: Uses `SET LOCAL search_path` to scope each database transaction
4. **No cross-tenant access**: Database enforces isolation

### Isolation guarantees

| Guarantee          | Implementation                          |
| ------------------ | --------------------------------------- |
| Data isolation     | Separate PostgreSQL schemas             |
| Query scoping      | `SET LOCAL search_path` per transaction |
| No tenant spoofing | Tenant from JWT only                    |
| Audit separation   | Per-tenant audit tables                 |

## Audit trail

***

Matcher records all actions in an immutable, append-only audit log for compliance and forensics.

### Audited events

| Category          | Events                                         |
| ----------------- | ---------------------------------------------- |
| Data Access       | View matches, view exceptions, export data     |
| Data Modification | Create match, resolve exception, update rules  |
| Configuration     | Create context, modify source, change settings |

### Query audit logs

Use the governance audit log endpoints to retrieve audit records:

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/governance/audit-logs" \
 -H "Authorization: Bearer $TOKEN"
```

<Tip>API Reference: [List audit logs](/en/reference/matcher/list-audit-logs)</Tip>

## Actor mappings and privacy

***

Actor mappings associate system identifiers (such as JWT `sub` claims) with human-readable display names and email addresses. This improves audit log readability without storing personal data in every log entry.

### Manage actor mappings

The actor ID path parameter accepts up to 255 characters (matching the database column constraint). Leading and trailing whitespace is trimmed automatically. Values exceeding this limit are rejected with a `400 Bad Request` error.

```bash cURL theme={null}
curl -X PUT "https://api.matcher.example.com/v1/governance/actor-mappings/user-abc-123" \
 -H "Authorization: Bearer $TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
   "display_name": "John Doe",
   "email": "john.doe@company.com"
 }'
```

The upsert operation returns the persisted actor mapping directly in the response, so you can verify the saved values without a separate GET request.

<Tip>API Reference: [Upsert actor mapping](/en/reference/matcher/upsert-actor-mapping) | [Get actor mapping](/en/reference/matcher/get-actor-mapping) | [Delete actor mapping](/en/reference/matcher/delete-actor-mapping)</Tip>

### GDPR pseudonymization

To comply with right-to-erasure requests, pseudonymize an actor to replace their personal data with `[REDACTED]`. This action is irreversible.

```bash cURL theme={null}
curl -X POST "https://api.matcher.example.com/v1/governance/actor-mappings/user-abc-123/pseudonymize" \
 -H "Authorization: Bearer $TOKEN"
```

<Tip>API Reference: [Pseudonymize actor](/en/reference/matcher/pseudonymize-actor)</Tip>

### Audit log archives

Historical audit logs are periodically compressed and moved to long-term storage. Use the archive endpoints to list and download archived data for compliance reviews.

```bash cURL theme={null}
curl -X GET "https://api.matcher.example.com/v1/governance/archives" \
 -H "Authorization: Bearer $TOKEN"
```

<Tip>API Reference: [List archives](/en/reference/matcher/list-archives) | [Download archive](/en/reference/matcher/download-archive)</Tip>

## Data encryption

***

### Encryption in transit

TLS encrypts all data transmitted to and from Matcher.

| Requirement   | Configuration               |
| ------------- | --------------------------- |
| Protocol      | TLS 1.2 or higher           |
| Cipher suites | Strong ciphers only         |
| Certificate   | Valid CA-signed certificate |
| HSTS          | Enabled with 1-year max-age |

### Encryption at rest

| Data Type    | Encryption Method                            |
| ------------ | -------------------------------------------- |
| Database     | PostgreSQL TDE (Transparent Data Encryption) |
| File storage | AES-256 encryption                           |
| Backups      | Encrypted before storage                     |
| Secrets      | Vault with envelope encryption               |

## SOX compliance

***

Matcher maintains records for SOX (Sarbanes-Oxley) audit requirements.

### SOX control features

| Control                   | Matcher Feature                           |
| ------------------------- | ----------------------------------------- |
| **Segregation of duties** | RBAC with granular permissions            |
| **Change management**     | Audit trail for all configuration changes |
| **Access control**        | JWT authentication with role enforcement  |
| **Audit trail**           | Immutable append-only logs                |
| **Data integrity**        | Transaction checksums and validation      |

## API security

***

### Rate limiting

Some endpoints include additional rate limiting to protect against abuse:

| Endpoint Type       | Rate Limited |
| ------------------- | ------------ |
| Exception dispatch  | Yes          |
| Export endpoints    | Yes          |
| Callback processing | Yes          |

### SSRF protection

Matcher blocks outbound HTTP requests to private IP ranges when dispatching exceptions to external systems. This prevents Server-Side Request Forgery (SSRF) attacks.

Blocked ranges include `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, and IPv6 equivalents.

### Webhook signature verification

Matcher signs outbound webhook payloads with HMAC-SHA256 using a shared secret. The signature appears in the `X-Signature-256` header, allowing receivers to verify authenticity.

## Best practices

***

<AccordionGroup>
  <Accordion title="Use least privilege access">
    Grant users only the permissions they need. Start with minimal access and add permissions as needed.
  </Accordion>

  <Accordion title="Rotate credentials regularly">
    Implement automatic rotation for service credentials and JWT secrets. Use short-lived tokens where possible.
  </Accordion>

  <Accordion title="Enable audit logging">
    Keep audit logs enabled and review them regularly. Set up alerts for suspicious activity.
  </Accordion>

  <Accordion title="Use rate limiting">
    Keep default rate limits enabled to protect against abuse. Adjust thresholds based on expected traffic patterns.
  </Accordion>

  <Accordion title="Review access regularly">
    Conduct periodic access reviews. Remove access promptly when users change roles or leave.
  </Accordion>

  <Accordion title="Verify webhook signatures">
    Always validate HMAC-SHA256 signatures on webhook payloads to confirm they originate from Matcher.
  </Accordion>

  <Accordion title="Monitor security events">
    Set up real-time monitoring and alerting for security events. Investigate anomalies promptly.
  </Accordion>
</AccordionGroup>

## Next steps

***

<CardGroup cols={2}>
  <Card title="Match rules" icon="scale-balanced" href="/en/matcher/configuration/matcher-match-rules">
    Configure matching rules securely.
  </Card>

  <Card title="Exception routing" icon="route" href="/en/matcher/configuration/matcher-exception-routing">
    Set up secure exception workflows.
  </Card>
</CardGroup>
