# Security

> What Truenote keeps, how it's protected, and what agents can do: read-only to banks, sealed Plaid tokens, per-agent grants, an append-only audit log, row-level security, and an honest list of known gaps.

Canonical: https://usetruenote.com/security

Boring on purpose: few secrets, each one sealed or hashed, every access scoped and logged, and nothing that can move money.

## The short version

- **Truenote can't move money.** It uses only Plaid's read products (transactions, balances, investments, liabilities, recurring). No transfer, payment or "auth for payments" endpoint exists in the code, and no agent tool writes to a bank.
- **Your bank logins never reach Truenote.** You sign in to your bank inside Plaid Link; Truenote gets a Plaid access token for reading, sealed with AES-256-GCM before it touches the database.
- **Agents get only what you allow,** per connection: which kinds of data (scopes), and which accounts, businesses or manual assets they must never see. Change or revoke that in one click; it applies to the agent's very next call.
- **Every agent call leaves a receipt** in your Activity log: which agent, what it asked, how many rows, whether it was refused.
- **You can take everything out** (Settings → Your data) **or delete everything** (Settings → Delete account), which also tells Plaid to forget every bank.

## What's stored

| Data | Protection |
|---|---|
| Email, name, sessions, passkeys | Signed cookies (httpOnly, SameSite, Secure). Passkeys are public keys; the private key never leaves your device. |
| Sign-in codes | Six digits, 10 minutes, 3 tries, stored hashed. |
| Plaid access tokens | AES-256-GCM, random IV, the row's identity bound in. Keys in the server's environment, never the database. Opened only in memory before a Plaid call. Never returned by any endpoint, tool, export or log. |
| Documents | Sealed like Plaid tokens. Agents need a documents scope; the text they get has Social Security and long account numbers masked to the last four. |
| Bank data and your context | Rows in Postgres, scoped to you by the API and by row-level security. Rows an agent wrote say which agent. |
| Agent access | Every bearer secret is a random 256-bit string stored only as its SHA-256 hash. Shown once. |
| The audit log | Append-only: the database refuses updates and deletes. Arguments summarized, never secrets. |

Not stored: bank passwords, card numbers, any token in the clear.

## Who can reach it

**You:** an emailed code or a passkey, no passwords; rate-limited. A session lasts 14 days from last use. Managing agents, exports and deleting the account need your browser session; an agent's token can never do them.

**Agents:** OAuth 2.1 (PKCE, exact redirect URIs, tokens bound to the endpoint) or a personal access token. One grant per connection with scopes (accounts, transactions, investments, liabilities, context read/write, documents read/write) and exclusions (accounts, businesses, manual accounts) filtered in SQL in every read, totals included, and checked on every write. New connections start read-only. Agents can write the spending plan, buckets (Truenote's ledger only), rules and Review decisions, profile, entities, goals, income sources, notes, plans, categories; never a bank, money, your sign-in, other agents' access, the audit log, exports or your account. 240 calls a minute per grant.

| Token | Lifetime |
|---|---|
| OAuth authorization code | 5 minutes, once |
| OAuth access token | 1 hour |
| OAuth refresh token | 30 days, rotated on every use |
| Personal access token | 7, 30, 90 or 365 days, or until revoked; flagged after 90 days unused |
| Web session | 14 days from last use |

## Defense in depth

1. The API is the only door; the web app never touches the database or Plaid.
2. Every query is scoped in code to the signed-in user; someone else's id is a 404.
3. Row-level security in Postgres: a query that forgot its filter still gets only your rows.
4. Same-user foreign keys.
5. A strict Content Security Policy, no framing, HSTS; agent endpoints ignore cookies and refuse unknown browser origins.
6. Logs and error reports never carry secrets or bank text.

## Known gaps

- Bank text isn't filtered by exclusions (a visible transaction may name a hidden account). Free text you write isn't filtered either.
- Background jobs run outside row-level security.
- Backups keep deleted data until they age out (6 to 89 days).
- One person per account; household sharing with separate grants is planned.
- No independent security review yet; one is planned.

## Deleting your account

Every bank is removed at Plaid, every agent's access revoked, the audit log purged, everything deleted. Backups keep their copies until they expire.

Report an issue: security@usetruenote.com
