Security and data flow
Where your data lives, what is encrypted, how the MCP connection is authenticated and scoped, what Assetli logs, what the AI provider sees, and the limits we have not closed yet.
Last updated: September 17, 2026
This page is written for a sceptical, technical reader. Every statement below was checked against the application code on the date shown above. Where something is a limitation rather than a feature, it is listed under Known limitations and marked as planned.
Summary
Two companion pages go deeper: Who processes your data lists every third party and what it receives, and How long data is kept gives the retention of every record.
- Assetli never connects to your bank and never holds bank credentials. Data arrives by file import, manual entry, or an optional Wallet by BudgetBakers sync using that app’s token.
- The MCP connection uses OAuth 2.1 with PKCE, a consent screen, short-lived tokens stored as hashes, one profile per connector, and separate read and write scopes. Writing requires a paid plan.
- Your AI conversation never reaches Assetli. The server receives tool calls only. Reads are not logged; writes are logged for 12 months.
- No third-party error-tracking or analytics SDK runs on the API. Browser analytics on the website are consent-gated and never see API traffic.
Where data lives
| What | Where |
|---|---|
| Application and API | Vercel, Frankfurt region (fra1) |
| Database | MongoDB Atlas |
| Uploaded files (statements, photos, documents) | Object storage: Vercel Blob or Cloudflare R2, depending on the deployment configuration |
| Public reference data (price lists, statistics) | Cloudflare R2 |
| Daily database backup | mongodump archive encrypted with AES-256 before it leaves the build runner, stored as a GitHub Actions artifact |
| Exchange rates | fetched from frankfurter.app (ECB rates); the request carries only currency codes |
| Payments | Stripe; card data never touches Assetli |
Signing in to Assetli
- Email and password. Passwords are hashed with bcrypt.
- Google or Facebook sign-in (OAuth).
- Optional two-factor authentication with a TOTP app and backup codes. The TOTP secret is stored encrypted.
- Accounts are locked after repeated failed sign-in attempts.
- Sessions are HTTP-only cookies issued by Auth.js.
Encryption
- All traffic is HTTPS.
- Field-level encryption with AES-256-GCM (versioned ciphertext, random IV, authentication tag) for: account number, IBAN and notes of bank accounts; the Wallet by BudgetBakers token; two-factor secrets; your own AI provider API keys; Family Legacy notes. Encrypted fields cannot be searched in the database and are decrypted only when the record is displayed.
- OAuth access and refresh tokens, authorisation codes and API keys are stored as SHA-256 hashes. The plaintext exists only in your client.
The MCP connection, step by step
This is what happens when you ask Claude or ChatGPT a question that needs your data.
- Your client calls
POST /api/mcpwith a JSON-RPC body: the tool name and its arguments. Nothing else from the conversation is sent. - Middleware passes the request through; there is no site-wide password on this path and no request-body logging.
- Token verification. The
Authorization: Bearertoken is hashed and looked up in the database. It must exist, be unexpired (1 hour), not revoked, and its audience must match the host. The record yields your user id, the profile chosen at consent, the scopes and the client id. - Scope, plan and preview token. A write tool requires the
writescope and a Premium or Max plan. Without either, the call returns an error and nothing runs. Without both, write tools are not even listed to the client. A write call must also carry a confirmation token issued by its preview tool for the same arguments within the last 10 minutes; without it, or with different arguments, the write is refused. - Tool execution. The handler queries MongoDB with a filter on your user and profile. Results are mapped into a fixed set of fields.
- Audit (writes only). The tool name, client id, result and a summary of the arguments are stored in
mcp_audit_logswith a 365-day expiry. Amounts, categories, symbols, dates and target names are kept; free text (descriptions, notes, merchant names, statement contents) is stored only as its length, statement contents also as a hash. The record itself is tagged with the origin (createdVia: MCP and the connector id), and every list that shows it (transactions, trades, meter readings, valuables, budgets) marks it with an “AI” badge whose tooltip names the channel and the tool. - Response. The JSON result goes back to your client, which feeds it to the model.
What Assetli sees and does not see
| Seen by Assetli? | |
|---|---|
| Your prompt and the model’s answers | No. They stay in the AI client. |
| Tool name and arguments | Yes, for the duration of the request. |
| Arguments of write calls | Yes, as a summary without free text, in the audit log for 12 months. |
| Arguments of read calls | Not stored. |
| Tool results | Computed and returned; not stored. |
| Client identity | The connector’s registered name and client id; the IP and user agent of the AI client’s server, in standard hosting request logs. |
“Same as clicking in the app” is accurate for reads. For writes Assetli keeps slightly more than a web click would: the audit record.
What the AI provider sees
The provider that runs your model (Anthropic for Claude, OpenAI for ChatGPT, Google for Gemini) sees your prompts, the tool results, and therefore every figure a tool returns. That data is handled under that provider’s terms, not Assetli’s. The tools return the same data you see in the app: balances, transactions with descriptions and merchants, holdings, property values and addresses, vehicle licence plates, debts. They never return account numbers, IBANs, passwords, tokens or other users’ data.
The in-app assistant works differently: there Assetli itself sends your snapshot and messages to Anthropic (or to the provider whose key you entered) and stores the conversation in your profile. See AI assistant.
Token scoping
- One user, one profile. The token carries the profile you picked at consent. A connector cannot switch profiles.
- Read or write. Granted on the consent screen; write implies read. The server checks the scope on every call.
- Audience-bound. A token issued for
assetli.appis rejected by any other host. - Short-lived. Access token 1 hour; refresh token 60 days, rotated on every refresh.
- Revocable. the account menu (top right) → AI Connection (MCP) lists every connector and removes all of its tokens at once.
- No secrets shared. Clients are public OAuth clients with PKCE; there is no client secret and your password never leaves Assetli.
- MCP endpoint only. A third-party connector token is accepted by
/api/mcpand nowhere else. The REST API accepts Bearer tokens only from the first-party mobile client.
The same OAuth server will also issue tokens for the Assetli mobile app, which is still in development. It is a first-party client and skips the consent screen, but goes through the same sign-in, PKCE and token checks.
Logging
What is written, and where:
| Log | Content | Retention |
|---|---|---|
| MCP audit log (database) | User, profile, client id, tool name, success or error, a summary of the arguments without free text. Writes only. Visible under the account menu → AI Connection (MCP). | 365 days |
| Hosting request logs (Vercel) | Method, path, status, timing, client IP. No bodies. | Per Vercel plan settings |
| Function output (Vercel) | Error messages printed by the application on failures, for example a database error. Not request bodies. | Per Vercel plan settings |
| Admin error log (database) | Application errors from selected endpoints and from the web front end. Not used on the MCP path. | Managed by the admin |
What is not there:
- No Sentry, Datadog, PostHog or similar SDK in the API. There is no dependency of that kind in the project.
- No middleware that captures request or response bodies.
- Google Analytics and Microsoft Clarity load only in the browser on website pages after cookie consent. API and MCP traffic never reaches them.
Other integrations
- Wallet by BudgetBakers (Premium): you paste Wallet’s API token, Assetli stores it encrypted and only reads from Wallet’s API. Assetli never writes to Wallet.
- API keys (
ask_…): a separate, read-only key type for the JSON overview endpoint and the energy Data API. Stored as SHA-256 hashes; can be limited to read, given an expiry, and revoked in Settings. Not used by the MCP connection. - Google Sheets add-on: its own token type, accepted by two mortgage endpoints only; listed and revocable on the same connections page.
- Chrome extension for the property check: reads the listing page open in your browser and sends its text to the analysis endpoint. The server never fetches listing sites itself.
Your data rights
- Export: Settings → Export data produces a file with every record you own across all collections, excluding authentication secrets and transient logs.
- Deletion: Settings → Delete account removes the user and every record keyed to it, including group-owned and legacy-owned records, and files. The confirmation phrase is required.
Known limitations and planned changes
We would rather list these than have you find them.
- Confirmation is enforced as a pairing, not as consent. The server refuses a write that has no token from the matching preview for identical arguments within 10 minutes. Whether you actually saw the preview and said yes is still up to the AI client; Claude and ChatGPT add their own tool-approval prompts.
- Log retention on the hosting side depends on the Vercel plan and is not controlled by the application.
Reporting a security issue
Write to info@assetli.app with “security” in the subject. Please do not test against other users’ data.