Libertas is built on a single principle: your financial data belongs to you and only you.
This page documents exactly what that means in practice — what's encrypted, what leaves your machine (nothing by default), and how the optional integrations are designed to stay in your control.
Libertas is a local tool. The threats it guards against are:
| Threat | Mitigation |
|---|---|
| Physical access to your machine | AES-256-GCM encryption on all sensitive DB fields |
| Stolen or leaked database file | Encrypted fields unreadable without the derived key |
| Third-party data breach | No third party holds your data |
| Credential phishing | No bank OAuth — you import CSVs only |
| Unintended data exfiltration | Zero telemetry, zero analytics, no outbound calls by default |
What Libertas does not protect against: a fully compromised OS, malware with keylogger access, or an attacker who can already run code as your user. Those are OS-level threats outside any app's scope.
All sensitive text fields in SQLite are encrypted with AES-256-GCM before writing to disk. Every field is encrypted individually with a unique random nonce, so identical values produce different ciphertext.
| Field type | Encrypted |
|---|---|
| Account names and external IDs | ✅ Yes |
| Institution names, URLs, and notes | ✅ Yes |
| Transaction descriptions and ticker symbols | ✅ Yes |
| Holdings symbols | ✅ Yes |
| Property addresses | ✅ Yes |
| API keys (Claude, Plaid, News) | ✅ Yes |
| Account balance | ❌ No (required for SQL aggregation) |
| Transaction amounts | ❌ No (required for SQL aggregation) |
| Prices and interest rates | ❌ No (required for SQL aggregation) |
Numeric fields are stored plaintext to allow SQL aggregations (SUM, GROUP BY) that power the dashboard. An attacker with raw DB access sees account totals but cannot read account names or API credentials.
AES-256-GCM requires a 32-byte key. Where that key comes from depends on your security setting:
macOS Keychain (default)
A random 32-byte key is generated the first time you run Libertas. macOS stores it in your system Keychain — the same place Safari stores passwords. It's protected by your login password and unlocks automatically with Touch ID on supported hardware. The key is never written to the database or any file.
Passphrase (maximum security)
If you switch to passphrase mode in Settings → Data Security, Libertas derives the encryption key from your passphrase using Argon2id — the current gold standard for password-based key derivation. Argon2id is intentionally slow and memory-intensive, making brute-force attacks impractical.
A random salt is generated and stored in the database (not secret — it ensures the same passphrase produces a unique key per database). The derived key lives only in memory for the duration of the session.
::: warning If you use passphrase mode and lose your passphrase, the data cannot be recovered. There is no backdoor, no recovery key, no support ticket that can help. This is a feature, not a limitation — it means no one else can access your data either. :::
Decryption happens in memory, on demand, when:
- The app loads a page that reads from the database
- You run a search or filter
- Claude analyzes your data (see below)
Decrypted data is never written back to disk. It exists in RAM for the duration of the request, then is discarded.
By default, zero bytes of your financial data leave your machine.
Your machine
├── SQLite database (/data/libertas.db) ← encrypted at rest
├── CSV imports (/data/watch/) ← processed locally, never uploaded
└── Encryption key (macOS Keychain) ← never transmitted
The backend binds to 127.0.0.1 only. It is not accessible from other machines on your network.
When you use AI chat or ask for AI-powered insights, here's exactly what happens:
Your question
↓
Libertas queries the database
↓
SQLAlchemy decrypts matching rows in memory
↓
Decrypted results are formatted as context
↓
Context + your question → Claude API (HTTPS/TLS 1.3)
↓
Claude analyzes and responds
↓
Response displayed in app
Claude never:
- Touches the database file
- Receives encrypted ciphertext
- Stores or remembers your data between sessions
- Has access to your encryption key
Claude only receives:
- The specific rows needed to answer your question
- Your question and any context you explicitly provide
API calls to Claude are encrypted in transit with TLS 1.3. Anthropic's data retention policy applies — review it at anthropic.com/privacy if this matters to your threat model.
Go to Settings → Data Security to switch between modes. When you switch to macOS Keychain mode, the key is initialized immediately. When you switch to Passphrase mode, you set your passphrase there — existing data will be re-encrypted on the next write using your new key.
Every integration that touches an external network is explicitly opt-in and requires you to set an API key in Settings.
- Used for: live market news headlines
- What's sent: HTTP request for news articles — no user data included
- Falls back to: public RSS feeds if no key is set
- Used for: direct bank connection (read-only balance + transaction sync)
- What's sent: OAuth flow via Plaid's hosted Link UI — Libertas never sees your bank credentials
- Disable by removing the Plaid key from Settings
Libertas contains no analytics, no crash reporting, no usage tracking of any kind. There is no "phone home" on startup, no event logging to a remote server, no A/B testing infrastructure.
If you run Libertas in airplane mode, it works identically to connected mode — except optional network integrations will be unavailable.
- Cipher: AES-256-GCM (authenticated encryption — detects tampering)
- Key derivation (passphrase mode): Argon2id, 3 iterations, 64 MB memory, 4 threads, 32-byte output
- Keychain integration: Python
keyringlibrary → macOS Security framework - Encrypted value format:
enc1:prefix + base64(12-byte nonce + ciphertext + 16-byte GCM tag) - Implementation:
backend/services/encryption.py,backend/models.py(EncryptedTextTypeDecorator) - ADR: ADR-010 — At-Rest Encryption
All of this is verifiable. The full source code is on GitHub. The encryption implementation is in backend/services/encryption.py.
Found a security issue? Open a GitHub issue or email the maintainer directly. There is no bug bounty program — this is a personal open-source project.