Skip to main content

Security posture

Stoic Budget is self-hosted: your bank statements, receipts and categories stay on your server. This page says plainly what the app protects against, what it encrypts, what it sends anywhere and how to turn the outbound parts off.

Threat model​

We defend againstHow
Another workspace's member reading your dataEvery query filters by workspaceId; tenant isolation is tested end to end
Strangers signing up on a server open to the internetClose sign-up under Admin > Users, or with REGISTRATION_ENABLED=false: only people with an invitation get in
A stolen session cookieShort-lived JWT in an HttpOnly cookie, refresh rotation, CSRF guard on every mutating route, 2FA with recovery codes
An over-reaching script or AI agentAPI keys carry scopes; a key never exceeds its owner's role; every write is audited as the key and is undoable
The server being used to reach your networkThe egress guard rejects private, loopback and link-local hosts for every outbound URL (SMTP, S3, WebDAV, IMAP, prices, maps)
Uploaded files executing anythingFiles are stored, never served as scripts; parsers run on text extracted from them

We do not defend against an administrator of the host itself, a compromised database backup stored in clear, or a browser extension reading the page.

What is encrypted​

  • Passwords: bcrypt hashes. API keys: SHA-256 hashes, the raw key is shown once and never stored.
  • Integration secrets (SMTP, S3, WebDAV, IMAP, Telegram): encrypted at rest with the server's INTEGRATIONS_ENCRYPTION_KEY.
  • 2FA secrets and recovery codes: encrypted / hashed.
  • Transport: run behind TLS; the cookie is Secure in production.
  • Statements and receipts on disk are not encrypted by the app; use disk encryption and encrypted backups on the host.

Accounts and sessions​

  • Sessions live in HttpOnly cookies: access token (30 min) + refresh token (30 d), tracked per device; the login response body carries no token.
  • Refresh tokens rotate on every use and old tokens are invalidated.
  • Double-submit CSRF protection for cookie-authenticated writes; header-authenticated clients (Authorization, X-Api-Key) are exempt.
  • Optional TOTP two-factor authentication with recovery codes.
  • Single-use password reset (1 h) and email change (24 h) tokens; the reset endpoint answers the same way for known and unknown addresses.
  • Bcrypt password hashing (12 rounds by default, BCRYPT_ROUNDS).
  • helmet security headers, a CORS allowlist and Redis-backed rate limits.
  • Workspace roles are enforced on every request by guards at the controller level.
  • The audit log records most mutating operations, with rollback for critical changes.

What leaves your server, and when​

DestinationSentWhenOff switch
Exchange-rate providerCurrency pairs and datesMulti-currency totalsSingle currency, or set rates by hand
Yahoo Finance / CoinGeckoTickers"Refresh prices" on the net-worth pageEnter prices by hand
Your map tile server / NominatimReceipt coordinatesReceipt maps, if enabledDo not enable receipt location
TelegramReports and replies to the botOnly if the bot is connectedDisconnect Telegram
SMTPInvitations, password resetsOnly if SMTP is setLeave SMTP unset

Nothing is sent to Stoic Budget's authors. There is no telemetry.

No external LLM is ever contacted. Statement parsing and categorisation run on your server: parsers are deterministic, and categories come from your rules, your corrections and payee history.

Agents, keys and the record​

See MCP and API keys: scopes per key, the Integration actor in the activity log, undo on every write rollback understands.

Reporting a problem​

Open a private security advisory on the GitHub repository. Please do not file public issues for vulnerabilities.