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

# Row-Level Security (RLS)

> Defense-in-depth tenant isolation at the database layer.

Truthlocks uses Postgres Row-Level Security (RLS) as a defense-in-depth measure to enforce tenant isolation at the database layer. Even if application code has bugs, cross-tenant data access is prevented by the database itself.

<Note>
  RLS is enforced in addition to application-level tenant filtering. Both layers
  work together to ensure data isolation.
</Note>

## How It Works

### Session Variable

Each database request sets a session variable with the verified tenant ID:

```sql theme={null}
SET LOCAL app.tenant_id = 'verified-tenant-uuid';
```

### Policy Definition

RLS policies check that rows match the session tenant:

```sql theme={null}
CREATE POLICY tenant_isolation ON issuers
  USING (tenant_id = current_setting('app.tenant_id')::uuid)
  WITH CHECK (tenant_id = current_setting('app.tenant_id')::uuid);
```

### Tenant Identity Source

The tenant ID is derived **only** from verified authentication context:

* JWT claims (after signature verification)
* API key lookup (after hash validation)
* **Never** from client-provided headers like `X-Tenant-ID`

## Covered Tables

RLS is enabled on all tenant-scoped tables:

| Table                | Tenant Column | Policy                                              |
| :------------------- | :------------ | :-------------------------------------------------- |
| `issuers`            | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |
| `issuer_keys`        | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |
| `policies`           | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |
| `tenant_api_keys`    | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |
| `attestations`       | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |
| `risk_signals`       | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |
| `risk_event_sources` | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |
| `risk_decisions`     | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |
| `fraud_cases`        | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |
| `fraud_events`       | `tenant_id`   | <span style={{ color: '#16a34a' }}>✓ Enabled</span> |

## Fail-Closed Behavior

If `app.tenant_id` is not set in the session:

* **SELECT**: Returns zero rows (not an error)
* **INSERT**: Fails with RLS policy violation
* **UPDATE/DELETE**: Affects zero rows

## Verification

Run the RLS isolation test suite:

```bash theme={null}
make rls-check
```

## Implementation Notes

### Connection Pooling

When using connection pools, ensure that `SET LOCAL` is used within a transaction.

```go theme={null}
// Go example with pgx
tx, _ := pool.Begin(ctx)
defer tx.Rollback(ctx)

tx.Exec(ctx, "SET LOCAL app.tenant_id = $1", tenantID)
// ... perform queries ...

tx.Commit(ctx)
```
