Information Security Policy
System: Koda Finance (Android app + Firebase backend) · Owner: Ivy, Founder, Koda Digital · Contact: contact-us@kodadigital.ai · Effective: 2026-09-03 · Review: every 6 months
1. Purpose and scope
This policy governs how consumer financial data obtained through Plaid is protected in Koda
Finance. It covers the mobile app, the backend (Firebase Cloud Functions + Firestore in
Google Cloud project koda-finance-e1c74, region us-central1), the Plaid integration, and
the operator's workstation and accounts.
2. Context
Koda Finance is a personal net-worth tracker in a private beta. One person is its sole developer and administrator (the Operator); the app is sent directly to a small number of invited testers and is not in the app stores. There are no employees, contractors, or third-party integrators. Each tester has their own account and sees only their own data; none has any administrative access.
3. Architecture (the basis for every control)
Android app ──TLS──▶ Cloud Functions (api) ──TLS──▶ Plaid API
│ sign-in: password │ ID token check: verified, ▲
│ + authenticator │ not revoked, MFA; uid scopes data
│ biometric lock ├──▶ Firestore (tokens, cache, manual assets, history)
│ keystore: session, └──▶ Secret Manager (Plaid keys)
│ last NW summary,
│ ledger key
│ SQLCipher DB: transaction ledger (never on the server)
├── Firebase Authentication (Identity Platform, TOTP MFA)
└── Plaid Link SDK (bank credentials never touch Koda code)
- Bank credentials are entered only inside Plaid Link; Koda never sees them.
- Plaid access tokens exist only in Firestore, readable only by the backend service account.
- The only financial data on the device is the last net-worth summary (totals and per-account balances, no transactions), encrypted in the iOS Keychain / Android Keystore so the app opens offline, and erased on sign-out and account deletion. Every other screen loads from the backend.
- Crash reports go to Sentry only when a DSN is configured, and carry no account data, balances, transactions, email, screenshots or IP address.
- The account holder can delete their account and all data from inside the app; see data-retention-and-deletion.md.
- All transport is HTTPS/TLS 1.2+ (Google-managed certificates on cloudfunctions.net; Plaid's on plaid.com).
4. Zero-trust access architecture
No network location is trusted. There is no VPN, office network, or IP allow-list that grants access; every request to every component is authenticated and authorised individually:
- App → backend: each HTTP request carries the signed-in user's Firebase ID token, checked by middleware (
functions/src/firebaseAuth.ts) for validity, revocation, a verified email, and a completed authenticator (TOTP) step. Anything else receives 401/403 and no data. The uid in that token is the only key used to reach Firestore (users/{uid}), so one user can never read another's data, whatever the request asks for. - Plaid → backend: none. Plaid never calls the backend; every Plaid request is made by the backend on a signed-in user's behalf.
- Backend → Firestore / Secret Manager: Google-issued short-lived service-account credentials, scoped by IAM; Firestore security rules deny all direct client access (
firestore.rules). - Backend → Plaid: client ID + secret from Secret Manager, per request, over TLS.
- Operator → Google Cloud / Firebase / Plaid dashboards: Google account with 2-Step Verification (phishing-resistant method), Plaid dashboard with MFA, Sentry (if crash reporting is enabled) with MFA. No standing sessions on shared machines.
- Device: the session is stored in the Keychain/Keystore, excluded from backups. As a second layer on an open session, the app requires device biometric authentication on every launch and re-locks when backgrounded (
app/src/components/lock-screen.tsx).
5. Risk identification, mitigation and monitoring
| Risk | Mitigation | Monitoring |
|---|---|---|
| Backend URL discovered and scanned | Every route except the public health and minimum-version checks requires a valid, MFA-verified session, which reaches only its own data; 401/403 otherwise | Cloud Functions logs; Cloud Monitoring error rate |
| App bundle copied or decompiled | The bundle contains no credential — only the Firebase project identifiers — so a copy grants nothing without the account password and an authenticator code | Firebase Authentication sign-in activity |
| Operator password stolen | Authenticator (TOTP) code also required; sessions opened without it are refused by the backend | Firebase Authentication sign-in activity |
| Plaid credential leak | Secrets only in Google Secret Manager; never in git (.gitignore), never in the app |
Secret Manager access audit logs |
| One user reaching another's data | Data is stored per uid and routes read only the caller's own tree; Items are looked up under the caller's uid, so an Item id belonging to someone else is simply not found | Code review of every route that touches Firestore |
| Lost/stolen phone | Biometric lock on the open session; no offline data; "sign out of every device" (or disabling the user in Firebase Authentication) revokes the phone's session on its next request; device can also be wiped remotely | Firebase Authentication sign-in activity |
| Operator account compromise | 2SV with passkey/security key on Google; MFA on Plaid; unique passwords in a password manager | Google security alerts |
| Data over-retention | Cache TTLs and automatic purge; unlink endpoint revokes Plaid Items (see data-retention policy) | Scheduled job logs |
6. Change management
Code lives in a private GitHub repository. Changes are deployed only by the operator via
firebase deploy after a TypeScript build passes. Dependencies are updated on a monthly
cadence or immediately on a published critical vulnerability.
7. Incident response
If compromise of the backend, secrets, or a device is suspected: (1) sign out of every device
(POST /revoke-sessions, or disable the user in Firebase Authentication), change the account
password and re-enroll the authenticator if either may be exposed, rotate the Plaid secret in
Secret Manager, and redeploy; (2) remove affected Plaid Items via
/item/remove (the app's unlink function) and re-link; (3) review Cloud audit logs for the
window; (4) notify Plaid via the dashboard support channel if Plaid data may have been
exposed. As the only affected consumer is the operator, no external consumer notification
is required; this section will be expanded before any multi-user release.
8. Related policies
See access-control-policy.md, authentication-and-secrets.md, data-retention-and-deletion.md, privacy-policy.md.