---
title: Security
persona: all
category: Technical documentation
version: 1.0.0
updated: 2026-06-09
---

# Security

MyFinAI's security rests on three walls that don't depend on anyone's discipline:
**tenant isolation** (RLS), **role isolation**, and **tamper-proof trails**
(chained audit + BYOK). Each one is verified continuously and re-runnably.

## Wall 1 · tenant isolation (RLS)

Every table with tenant data carries a `FORCE ROW LEVEL SECURITY` policy over
`app.current_tenant_id`, and the application connects with the `myfinai_app` role
(`NOSUPERUSER`, `NOBYPASSRLS`). A business's books and a household's are
physically invisible to each other.

**Proven live:** two workspaces —Café La Aurora and Hogar Marrero— with zero leak
in both directions. Their transaction sets are disjoint, and the same login that
switches workspaces gets a different set of books: café DR **$91,216** ≠ household
DR **$6,360**.

## Wall 2 · role isolation

RLS isolates *tenants*; roles isolate *people within a tenant*. The business
books (transactions, payroll, ledger, taxes, audit) sit behind the **books**
level (owner · bookkeeper · accountant · head of household). The **employee** and
the **family member** are left out: they only see their own pay stub or their own
allowance.

> This is **prevented** on the server, not in the interface. Even if an employee
> requested `GET /api/data/payroll` directly, the response is `403`. The same
> goes for Norte: nobody pulls the books out through the assistant.

## Wall 3 · verified membership

You can't obtain a token for a workspace you don't belong to. `/api/auth/switch`
validates the membership on the server: a café user gets a `403` when trying to
switch to the household, and vice versa. Membership is checked, not assumed.

## BYOK · your keys, encrypted

If a tenant brings its own AI key (BYOK), it's stored encrypted with
**Fernet + HKDF** (environment master key + per-tenant salt,
`info="myfinai-byok-v1"`). Only the ciphertext is stored; the master key
fails closed and the plaintext is never logged or returned. Without a key, the
deterministic agents keep working: the AI polishes the answer, it isn't a
requirement for the system to operate.

## Chained audit

Every relevant action —opening a period, a decision, provisioning, and whatever
each agent does— is written to `audit_log` with the hash of the previous event
(`prev_hash → hash`). Altering a past record breaks the chain and shows. The
audit covers operations, not just money.

## Continuous verification

All of the above is tested with `api/infra/audit_flows.py`: **77 checks, 0
failures** against the production system, covering the seven roles, the
cross-tenant test in both directions, and the reconciliation of both sets of
books. The record of findings, fixes and pending items lives in `api/AUDIT.md`.

```
python api/infra/audit_flows.py            # against production
BASE=http://localhost:8090 python …        # against a local instance
```

> **The idea:** trust isn't asked for, it's demonstrated. Anyone can re-run the
> test and see, on their own screen, that the walls are still standing.
