Skip to main content

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: October 30, 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.
  • Files you attach live in private storage in the EU with no public address and are shown only to their signed-in owner.
  • With two-factor authentication on, the code is required at every sign-in with a password. After a Google or Apple sign-in Assetli does not ask for a code; verification there is handled by your Google or Apple account. The Sign out of all devices button ends every session, including the mobile app and AI connections.
  • 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 or in the mobile apps (Android and iPhone). Google Analytics and Microsoft Clarity load on website pages only after cookie consent and never see API traffic; Vercel Web Analytics counts page views on the website and in the dashboard without cookies.

Where data lives

WhatWhere
Application and APIVercel, Frankfurt region (fra1)
DatabaseMongoDB Atlas, Frankfurt region (EU)
Files you attach (photos, documents, receipts)Cloudflare R2, a private bucket with EU jurisdiction (objects stay in the EU) and no public address. A file is shown only to its signed-in owner: the server checks the sign-in and that the record belongs to the active profile, then issues a signed link valid for at most 15 minutes, or streams the file itself (web preview, photos of valuables). The link itself needs no sign-in, which is why it is short-lived. Files uploaded earlier were moved to the EU on 30 October 2026 and the original copies deleted. Subscription and budget icons and temporary files are in Vercel Blob. Statements for import are not stored at all
Public reference data (price lists, statistics)Cloudflare R2, a separate public bucket (no user files)
Database backupmongodump archive encrypted with AES-256 (and checked that it decrypts), stored on the operator’s own computer in the Czech Republic; scheduled every night. Backups older than 30 days are deleted, but the last 7 backups are always kept, even if they are older. A second copy, encrypted before it leaves the build runner, is stored as a GitHub Actions artifact for 30 days, only when the job runs there (it has not run since 21 October 2026). The nightly backup is currently failing, see Known limitations
Exchange ratesfetched from frankfurter.app (ECB rates); the request carries only currency codes
PaymentsStripe; card data never touches Assetli

Signing in to Assetli

  • Email and password. Passwords are hashed with bcrypt.
  • Google or Apple sign-in (OAuth), in the phone app also natively.
  • Optional two-factor authentication with a TOTP app and backup codes. When it is on, the code (from the authenticator app, or a backup code) is required at every sign-in with a password, on the web and in the app, and when entering the operator’s admin area.
  • After a Google or Apple sign-in Assetli does not ask for a code, on the web or in the app. Verification there is handled by your Google or Apple account (a passkey, their two-step verification; Apple requires it for Sign in with Apple). We recommend turning on two-step verification or a passkey there.
  • The TOTP secret is stored encrypted, the same code cannot be used twice, and a backup code works only once.
  • After 5 failed attempts within 15 minutes the account is locked for 15 minutes; wrong two-factor codes count too, whether entered at sign-in, in the app or in Settings (turning two-factor off, new backup codes).
  • Sessions are HTTP-only cookies issued by Auth.js, valid for 7 days from last use.
  • Sign out of all devices (Settings on the web and in the app) ends every web session, revokes the tokens of the mobile app, the Google Sheets add-on and every AI connection (MCP), and removes push notification registrations. The same happens automatically after a password reset. Turning two-factor authentication on or off signs out your other devices and keeps you signed in only where you made the change. Other server instances pick up the change within 30 seconds.
  • Security e-mails are sent after a sign-in from a new device, after a password reset and when two-factor authentication is turned off. A device is recognised by its type, browser and operating system, without versions and without the IP address; the database keeps only a hash of these. The e-mail states the device, approximate country, time and sign-in method. These e-mails cannot be unsubscribed from.
  • The admin area lets the operator in only with two-factor authentication completed in the same session, and records every access in an audit log (see Logging below).

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; the payment details of shared-expense group members (account number or IBAN, bank name and code, crypto wallet address — a group’s public link does not receive them at all). Encrypted fields cannot be searched in the database and are decrypted only when the record is displayed. Also stored encrypted: the Sign in with Apple refresh token, Google Play purchase identifiers and your secret e-mail import address.
  • OAuth access and refresh tokens, authorisation codes and API keys are stored as SHA-256 hashes. The plaintext exists only in your client. The same goes for the tokens in password reset and e-mail verification links: the database holds only a hash, the readable link exists only in the e-mail, and each works once.

The MCP connection, step by step

This is what happens when you ask Claude, ChatGPT or Gemini a question that needs your data.

  1. Your client calls POST /api/mcp with a JSON-RPC body: the tool name and its arguments. Nothing else from the conversation is sent.
  2. Middleware passes the request through; there is no site-wide password on this path and no request-body logging.
  3. Token verification. The Authorization: Bearer token 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.
  4. Scope, plan and preview token. Every tool of the write category, previews (prepare_*) included, requires the write scope and a Premium or Max plan. Without the write scope the write tools are not listed to the client and a call returns SCOPE_REQUIRED. With the write scope they are listed on Free too, so that writing works right after an upgrade, but every call without a Premium or Max plan returns TIER_FEATURE and nothing runs. The plan check runs on every call, not in the tool list, so it also applies after a trial ends. 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.
  5. Tool execution. The handler queries MongoDB with a filter on your user and profile. Results are mapped into a fixed set of fields.
  6. Audit (writes only). The tool name, client id, result and a summary of the arguments are stored in mcp_audit_logs with 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) — transactions, trades, meter readings, valuables, budgets and categorization rules — and the lists of transactions, trades, meter readings, valuables and budgets mark it with an “AI” badge whose tooltip names the channel and the tool. Actions that change existing records rather than create one (pairing a transfer, applying a rule, moving envelope money, confirming a balance) are recorded in the audit log.
  7. 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 answersNo. They stay in the AI client.
Tool name and argumentsYes, for the duration of the request.
Arguments of write callsYes, as a summary without free text, in the audit log for 12 months.
Arguments of read callsNot stored.
Tool resultsComputed and returned; not stored.
Client identityThe 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.app is 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, after a confirmation, removes all of its tokens at once. Sign out of all devices in Settings revokes the tokens of every connector in one go.
  • 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/mcp and nowhere else. The REST API accepts Bearer tokens only from the first-party mobile client.

The same OAuth server issues tokens for the Assetli app for Android and iPhone, which is still in closed testing and not yet publicly available. You can sign in with e-mail and password, with Google or Apple directly in the app, or in the system browser; the password and two-factor code are checked by the same function as on the web, including the attempt lockout (for an account with two-factor authentication the app asks for the code after a password sign-in; not after a Google or Apple sign-in, where verification is handled by the Google or Apple account), and tokens are issued by the same function as /api/oauth/token (access 1 hour, refresh 60 days). The app skips the consent screen because it is our own. It keeps its tokens in the phone’s secure storage (Android Keystore, Keychain on iPhone), and you can revoke them on the same AI Connection (MCP) page or with Sign out of all devices.

Logging

What is written, and where:

LogContentRetention
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, from the web front end and from the mobile apps (Android and iPhone): the error message and stack trace, the page or screen, app and system version, the account ID as a pseudonym (no name or e-mail), IP address and user agent. Not used on the MCP path.12 months, then deleted automatically
Admin access log (database)Who, when, method, path and identifiers from the address (user or record ID, collection). None of the content the admin area displayed, no e-mail. The same request within 30 seconds is recorded once. Deleting an account does not remove the record that it was viewed.365 days, then deleted automatically

What is not there:

  • No Sentry, Datadog, PostHog or similar SDK in the API or in the mobile apps (Android and iPhone). 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. The one exception is a single sign-up event: when you create an account, our server reports it to Google Analytics — only if you gave analytics consent (otherwise the Analytics cookie does not exist and nothing is sent). It carries the anonymous Analytics ID and the page you came from, never your name, e-mail or financial data.
  • Vercel Web Analytics counts page views on the website and in the dashboard (not in the mobile app) without cookies and without consent; the visit identifier is a hash valid for one day. It never sees API or MCP traffic.

Other integrations

  • Wallet by BudgetBakers (Premium and Max, plus your own Wallet Premium account): you paste Wallet’s API token, Assetli stores it encrypted and only reads from Wallet’s API. Assetli never writes to Wallet. The token is deleted when you disconnect. Technically it would also allow writing; Assetli uses it only to read.
  • API keys (ask_…): a separate, read-only key type for the Data API (energy price lists, mortgage and savings rates). It gives no access to your financial data. 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. Up front it has access only to assetli.app; when you click its icon it requests permission for the site in the open tab, and Chrome asks you unless it was granted before. When you open its panel it sends the address of the open page to check whether you have analysed it before; it reads the page content only after you click “Analyze this page”.

Your data rights

  • Export (on the web and in the mobile app): 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. In the mobile app (Android and iPhone): More → Settings → Delete account. Details: Deleting your account.

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, ChatGPT and Gemini add their own tool-approval prompts.
  • Log retention on the hosting side depends on the Vercel plan and is not controlled by the application.
  • The automatic nightly database backup has been failing since 22 October 2026. We are fixing it. The second copy in GitHub Actions has not been created since 21 October 2026 either.
  • Development works against the production database. The operator accesses data for operations, bug fixes and development, and only to the extent necessary. Development and checking scripts run against the production database, and some of them go through user profiles (for example, the check that the AI shows the same figures as the website). While doing so the operator uses an AI coding assistant from Anthropic, so the output of such scripts may be processed by Anthropic as a processor. A separate development database is our biggest open item.
  • The database tool in the admin area can also edit and delete records. Getting into it requires two-factor authentication and is logged; it shows encrypted fields only as ciphertext, and passwords, OAuth tokens and API keys exist in the database only as hashes.
  • Known vulnerabilities in third-party libraries are not yet monitored automatically. We are setting up alerts.

Reporting a security issue

Write to info@assetli.app with “security” in the subject. Please do not test against other users’ data.