# Security · Finyvo

> How Finyvo is built underneath: where each piece of data lives, how the app authenticates without accounts or passwords, what protects the backend, and what we don't have yet.

Effective from: 6 de agosto de 2026 (2026-08-06)

This page is about how Finyvo is built underneath. The privacy policy answers what data of yours is handled, what for, and with whom; this is the how: where each thing lives, what protects it, which decisions we made, and which ones we haven't made yet. No seals, no superlatives.

This page is about how Finyvo is built underneath. The [privacy policy](/en/privacy) answers _what data of yours is handled, what for, and with whom_; this is the _how_: where each thing lives, what protects it, which decisions we made, and which ones we haven't made yet. No seals, no superlatives.

## The short version

| Question                                        | Short answer                                                                                                                |
| ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Where does your financial data live?            | On your iPhone and, if you leave sync on, in the private database of your own iCloud account. On no server of ours          |
| Is there an account or password?                | No. The app identifies itself with a cryptographic key that is born inside your iPhone's Secure Enclave and never leaves it |
| What protects the traffic?                      | HTTPS with TLS 1.2 or later, on every connection                                                                            |
| Where does the backend run?                     | On Cloudflare Workers, with D1 and KV as its only storage                                                                   |
| Where are the providers' keys?                  | Only on the backend, as Cloudflare secrets. None of them ships inside the app                                               |
| Can you turn off what goes out to the internet? | Yes. iCloud sync and the artificial intelligence features each have their own switch                                        |
| Any security certifications?                    | No. No SOC 2, no ISO 27001. The detail is in [What isn't there yet](#pendiente)                                             |

## On your iPhone

Almost all of Finyvo happens on your device. The database, the calculations, the budgets, the reports, the export: none of that needs a server.

**The database.** Your transactions, wallets, categories, tags, budgets, goals, and subscriptions live in **SwiftData**, Apple's local storage framework, which is **SQLite** underneath. The files sit inside the App's container with iOS data protection, in the `NSFileProtectionCompleteUntilFirstUserAuthentication` class: if your iPhone has a passcode, they are encrypted at rest until the first unlock after powering on.

**The preferences.** Language, theme, preferred currency, private mode, and the state of the artificial intelligence switches are stored in `UserDefaults`, the system's settings store. Nothing financial goes there.

**The Keychain.** We store exactly two things there, and neither one is data about you:

| What                               | Accessibility class                                | What it means in practice                                                                                                                                                                                      |
| ---------------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App Attest key identifier          | `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`     | Readable only while the iPhone is unlocked, and only on this device                                                                                                                                            |
| Backend session token (15 minutes) | `kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly` | Readable after the first unlock since you powered the phone on, so the app can do its background work — rescheduling alerts, refreshing rates — without waiting for you to unlock it. Also only on this device |

Neither is marked as synchronizable: **they do not travel to the iCloud Keychain** and are not restored on another device. By iOS design, Keychain items **can survive uninstalling the app**; they are removed when you reset the device.

**The private key isn't in that table, because we don't have it.** It lives inside the **Secure Enclave**, the iPhone's security coprocessor. Neither the App nor we can read it — we can only ask it to sign. What we keep in the Keychain is its identifier.

**The backups.** Before every internal format change to the database, the App saves a copy inside its own container and keeps up to **7, rotating**. They are local: they are not uploaded to any server of ours. If a format update fails, the app restores the latest copy on its own and tries again; you can also pick one by hand in _Settings → Advanced zone → Restore from backup_.

**Private mode, and how far it goes.** The eye icon on the Home screen hides every amount at once, including what VoiceOver reads out loud. It is a visual control: it does not encrypt anything and does not lock access to the app. Finyvo **does not include Face ID, Touch ID, or passcode locking**; the iOS passcode is the only door today.

## Your iCloud copy

If you leave sync on — it is on by default — your data is replicated to the **private database of your own iCloud account**, inside the `iCloud.com.moiszr.finyvo` container. That is Apple's infrastructure and the account is yours: Apple encrypts it in transit and at rest, and **Finyvo cannot query it**. It does not appear in any console of ours. If you have Apple's **Advanced Data Protection** enabled, that copy is additionally end-to-end encrypted.

You can turn it off in _Settings → Sync → iCloud_. The change applies the next time you open the app, and with it off your financial data does not leave the device.

**Turning it off does not delete what was already uploaded.** The App has no "delete my iCloud copy" button: to remove it you go to _iOS Settings → [Your name] → iCloud → Manage Account Storage → Finyvo_.

Exactly what gets replicated, and what happens with your display name, is in the [privacy policy](/en/privacy#icloud).

## How the app authenticates

Finyvo has no accounts. There is no email, no password, no login for anyone to guess, leak, or steal from you. The backend checks something different: that the caller is the real app, running on a real device.

**The key.** On first launch, the App generates a cryptographic key inside the **Secure Enclave**. The private half never leaves that chip: the app can't read it, iOS can't, and neither can we. It can only be asked to sign.

**The handshake**, step by step:

1. The app requests a challenge from `/auth/challenge`. The backend returns a random number that is **single-use, with a 5-minute lifetime**.
2. The app asks the Secure Enclave for an attestation over that challenge and sends it to `/attest/verify`. This is where **Apple** comes in: the attestation proves the key was born on a real device and that whoever is using it is a legitimate copy of Finyvo.
3. If it checks out, the backend **burns the challenge** — a used one is never accepted twice — and issues two things: an **installation identifier** and a **session token that expires after 15 minutes**.
4. From then on, every call to `/fx/*`, `/brand/*`, `/ai/*`, and `/import/*` travels with that token. **No feature of the App uses a shared anonymous token.**
5. When the token has a couple of minutes left, the app renews it on its own: new challenge, new Secure Enclave signature, new token. If a request crosses the expiry and comes back 401, it renews and retries once.

**The key that is inside the binary.** The three handshake requests cannot carry a session token yet — they exist to obtain one — so they identify themselves with an **application key shipped inside the binary and therefore common to every copy of Finyvo**. Anyone who extracts the binary can read it, which is why it is not the security boundary: it filters out background noise. The real boundary is the Secure Enclave attestation, which cannot be forged by copying a string. That key does not identify you either.

**The simulator.** App Attest does not exist in the Xcode simulator, so development builds have a shortcut to skip it. That shortcut **does not ship in the App Store binary**, and the backend only accepts it when a development secret is configured, which in production it is not.

**What the installation identifier is.** It is pseudonymous and persists for as long as the app stays installed; the backend uses it to count your monthly artificial intelligence quota, resolve whether you have Finyvo Plus, and block abuse. What it means as personal data, and what it is cross-referenced with, is explained in the [privacy policy](/en/privacy#identificacion).

## In transit

Everything travels encrypted with **HTTPS / TLS 1.2 or later**: between the App and our backend, and between our backend and external providers. There is no plaintext path.

Our backend requires TLS and always responds with these headers:

| Header                                                        | Value                                                                             | What it's for                                                                                         |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `Strict-Transport-Security`                                   | `max-age=31536000; includeSubDomains; preload`                                    | A year of mandatory HTTPS, subdomains included                                                        |
| `Content-Security-Policy`                                     | `default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'` | The API does not serve pages: it can't load resources, be embedded, or submit forms                   |
| `X-Frame-Options`                                             | `DENY`                                                                            | The same, for older browsers                                                                          |
| `X-Content-Type-Options`                                      | `nosniff`                                                                         | Nobody reinterprets a response's type                                                                 |
| `Referrer-Policy`                                             | `no-referrer`                                                                     | No address of ours leaks outward                                                                      |
| `Permissions-Policy`                                          | `geolocation=(), microphone=(), camera=()`                                        | The API asks for no sensors, and it is written down                                                   |
| `Cross-Origin-Resource-Policy` / `Cross-Origin-Opener-Policy` | `same-site` / `same-origin`                                                       | Isolates responses from other origins                                                                 |
| `Cache-Control`                                               | `no-store`                                                                        | Nothing is kept in intermediate caches. The exception is brand logos, which are public and are cached |

The `finyvo.com` site installs no cookies and loads no third-party resources: the fonts are hosted on our own domain.

## How the backend is built

`api.finyvo.com` runs on **Cloudflare Workers**: code that executes at the edge of the network, in the data center closest to you. There is no machine of ours running anywhere, and therefore no server to patch, no open port to watch, and no disk where things pile up through neglect.

The backend does two jobs: **proxy** to external providers and **gatekeeper** for the paid features.

Everything that persists fits in two services, and neither one holds financial information of yours:

| Where                        | What it stores                                                                                                                                                                                     |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Cloudflare D1 (SQL database) | Historical exchange rates, the installation registry, and technical artificial intelligence usage metrics: which feature, which model, how many tokens, how long it took, and whether it succeeded |
| Cloudflare KV (key-value)    | Caches for rates, symbols, and brand lookups, the handshake challenges, and the rate-limit counters. All with automatic expiry                                                                     |

Cloudflare encrypts at rest what it stores in D1. Outside of D1 and KV, the Workers have **no persistent storage**.

**The body of artificial intelligence requests is not stored.** Neither the request nor the response. All that remains is the technical metric in the table above.

**External providers' keys live only here**, as Cloudflare secrets: the exchange-rate provider's, Brandfetch's, and the language-model provider's. None of them ships inside the App or is handed to the client in any response. That is precisely why the backend exists as a proxy instead of the app calling each provider directly: a key embedded in an app installed on thousands of phones is a published key.

**Operational logs** — route, status code, duration, request identifier — are retained by Cloudflare for up to **30 days** and are not exported to any other service of ours. Which personal data can appear in them, and for how long, is detailed in the [privacy policy](/en/privacy#procesamiento).

**Internal health alerts** are sent through **ntfy** with operational metadata: status code, route, request identifier, and counters. **The IP address is not sent to that service**, and that is a design decision, not a coincidence.

## Limits and anti-abuse

Limits exist so that abuse can't take the service down or blow up a provider's bill. Today they are these:

| Limit                  | Scope                                                                               |
| ---------------------- | ----------------------------------------------------------------------------------- |
| 60 requests per minute | Per IP address, across the API routes                                               |
| 10 requests per minute | Per IP address, on `/attest/verify` only — the most expensive step of the handshake |
| 10 requests per minute | Per installation, on `/import/*`                                                    |

`/health` and non-API routes are exempt: the automated scanners that prowl any public domain don't consume counters.

There are no per-token limits anymore. They went away with App Attest: when the app used a shared static token, the limit belonged to the token rather than to the caller, so it measured nothing useful.

When something goes over the line, the answer is a **429** with the `Retry-After` and `X-RateLimit-*` headers, so the client knows how long to wait. And if the counter store goes down, the limiter **lets requests through** instead of blocking: between a failure of ours leaving you without exchange rates and a minute of requests going uncounted, we pick the second.

Separate from all this, and different: the free plan's **monthly artificial intelligence quota**, which the backend counts per installation. That is a plan limit, not a security one; it is described in the [privacy policy](/en/privacy#ia).

## The switches are yours

Two things send data off your device, and both can be turned off in Settings:

- **iCloud sync** — _Settings → Sync → iCloud_. With it off, your financial data does not leave the iPhone. The change applies at the next launch.
- **Artificial intelligence** — _Settings → Artificial intelligence_. One master switch and five per-feature ones. With the master off, no artificial intelligence call goes out, including the ones in the file import flow. The App falls back to its local engines, which work offline, and import goes straight to manual column mapping.

Two warnings about **when** that data leaves:

- Artificial intelligence categorization fires **when you leave the description field**, before you save. If you type a description and then cancel the transaction, that text and that amount have already been sent.
- Whatever you name a subscription is used as-is to look up its logo, and it leaves your device inside the request address. **Don't put personal data, or other people's data, in there.** You can change it any time from the editor.

**The way out.** _Settings → Data → Export my data_ generates a ZIP with your complete data inside the iPhone: offline, without passing through any server, and without needing Finyvo Plus. That file **comes out unencrypted and without a password**: the moment you share it or save it outside the app, it stops being protected by the App's container. Keep it somewhere safe.

**Deletion.** _Settings → Advanced zone → Reset all data_ empties the App's database. Note that this does not by itself remove the iCloud container: that is what the system settings above are for.

## What isn't there yet

Finyvo is built by one person. Here is what it does not have today, stated as precisely as what it does:

- **Certifications.** We have no SOC 2, no ISO 27001, and no equivalent seal. Those audits are designed for companies that hold their customers' data on their own servers; Finyvo does not hold yours — it lives on your iPhone and in your iCloud. If that model ever changes, this page will say so first.
- **External audit and bounty program.** We do not publish a pentest report and we have no bug bounty program. The channel for reporting is right below, and it is read.
- **In-app locking.** There is no Face ID, Touch ID, or app passcode. If someone has your iPhone unlocked, they can open Finyvo.
- **Encrypted export.** The export ZIP comes out in the clear.
- **Deleting your iCloud copy from the app.** That button does not exist; it is done from iOS settings.
- **Keychain items after uninstalling.** By iOS design they can survive uninstalling. They are two technical credentials, not data about you, and they are removed when you reset the device.

When any of these changes, this page changes with it.

## Reporting a security issue

If you find something — in the app, in the backend, or on the website — write to [support@finyvo.com](mailto:support@finyvo.com) with **Security** as the subject.

Tell us what you found and how to reproduce it. If you can, include the app version and the approximate time: it helps locate the request in the logs, which only live for 30 days.

What we ask of you: give us time to fix it before publishing, and don't access other people's data or degrade the service while you investigate. There is no monetary reward; there is public credit here, if you want it.

We reply within **30 calendar days** at most, the same commitment we apply to any privacy request.

---

Canonical (HTML): https://finyvo.com/en/security/
English · [Español](https://finyvo.com/security/) · [Português (Brasil)](https://finyvo.com/pt/security/)
Contact: support@finyvo.com · Site map for LLMs: https://finyvo.com/llms.txt
