Security Model
This page is the public summary of Bodhi App's security posture. It covers the guarantees the app makes by itself, the responsibilities it leaves to the deployment (your reverse proxy, your identity provider, your filesystem), and the hardening steps a self-hoster should take. The audience is operators making decisions about how to expose Bodhi to the network — not security auditors.
If you're choosing between desktop and Docker, Deployment → Overview will save you a step. If you're configuring TLS and rate limiting, jump to Deployment → Reverse Proxy.
Trust boundaries at a glance
Bodhi App is one process. There are four trust boundaries around it:
- Network ↔ reverse proxy. Your TLS-terminating proxy is the public face. Bodhi speaks plain HTTP behind it.
- Reverse proxy ↔ Bodhi. Anything that reaches the app is treated as authenticated-or-anonymous; the proxy is trusted to forward client IPs and not strip auth headers.
- Bodhi ↔ identity provider. OAuth2 PKCE flows go to your configured Keycloak realm. Bodhi never stores user passwords.
- Bodhi ↔ disk. The app's database, alias YAML, and HuggingFace cache live on a filesystem that you trust at the OS level.
Anything described below sits inside boundary 4. Anything outside is your responsibility.
What Bodhi protects against
Authentication
- OAuth2 with PKCE for all interactive logins. PKCE is mandatory — there is no legacy implicit or password-grant fallback.
- Session cookies are
HttpOnly,SameSite=Strict, and markedSecurewheneverBODHI_PUBLIC_SCHEME=https(so the browser will not transmit them over HTTP). - Session ID rotation on every successful login (and on dashboard re-auth) — defends against fixation attacks where an attacker pre-plants a session ID.
- API tokens are 32 bytes of cryptographically random data, prefixed with
bodhiapp_and a client-id suffix. They are stored as SHA-256 hashes; the plaintext is shown to you exactly once at creation. Token comparison is constant-time, so timing attacks against the hash table are not viable. - Token revocation — any token can be revoked instantly via the token management page; the next request with that token returns
401.
Authorization
- Role hierarchy with strict ordering:
User < PowerUser < Manager < Admin. Role-elevation attacks are mitigated by a role ceiling check — a Manager cannot delete or demote an Admin, regardless of how the request is shaped. - Status guards on access requests — only a
Pendingrequest can be approved or denied, so a re-played approval call cannot reactivate a closed request. - Per-route auth checks — every endpoint declares its required scope. The middleware refuses requests that don't carry an identity meeting that floor.
- Resource consent for external apps — third-party apps registered against Bodhi only see the scopes the user explicitly granted at consent time.
For the full role × endpoint matrix, see Reference → Roles and Scopes.
Encryption at rest
Sensitive credentials — API-model provider keys, MCP OAuth client secrets, MCP OAuth access/refresh tokens — are encrypted at rest in the application database using AES-256-GCM. The master key is read from the BODHI_ENCRYPTION_KEY environment variable; it is never written to the database.
If BODHI_ENCRYPTION_KEY is lost, encrypted credentials cannot be recovered — you must rotate the upstream secrets and re-enter them. Treat this variable like a database master password: store it in a secrets manager, back it up out-of-band, and never commit it to a repo.
Browser-side hardening
- Content Security Policy — Bodhi serves a strict CSP on its UI: scripts and styles are restricted to first-party origins, fonts are self-hosted (no Google Fonts CDN), and there are no inline event handlers in the shipped UI.
- No
javascript:URIs accepted. Any user-supplied URL field (access-request redirect URLs, MCP OAuth authorization endpoints) is validated server-side and re-validated client-side before navigation. - Cache-Control on token creation — the response that contains a freshly minted plaintext API token is marked
no-store, so it does not end up in the browser's disk cache or in proxies along the way.
Network egress
- URL scheme allowlist. Any URL Bodhi fetches outbound (provider APIs, MCP servers, OAuth endpoints) must be
http://orhttps://. Schemes likefile:,data:, andjavascript:are rejected. - Path validation. User-supplied filenames and aliases reject
..,/, and\to prevent traversal-based filesystem probing.
What Bodhi does not do (by design)
These are deliberate gaps you need to fill at the deployment layer.
Rate limiting belongs at the reverse proxy
Bodhi does not rate-limit at the application layer. The right policy depends on the deployment shape (a desktop install needs none; a Docker single-tenant on the public internet needs aggressive per-IP limits on /login and the API surface), and the right place to enforce it is the proxy that already sees every request before Bodhi does.
If you expose Bodhi publicly, configure rate limiting on nginx, Caddy, or your cloud WAF. See Deployment → Reverse Proxy for sample configs and recommended thresholds.
TLS termination belongs at the reverse proxy
Bodhi serves plain HTTP. TLS is terminated by the proxy in front of it. The Strict-Transport-Security (HSTS) header should be set by that proxy, not by Bodhi — an HSTS header on an HTTP response is ignored by browsers anyway.
Application-layer audit log shipping
Bodhi writes structured logs to disk (see Observability). It does not push logs to a SIEM, sign them, or enforce immutability. If you need tamper-evident audit logs, configure your log collector to ship $BODHI_HOME/logs/ to your central audit store with append-only semantics.
Cloud metadata protection
Bodhi allows outbound requests to private IPs by design — local LLM services (Ollama on localhost), self-hosted MCP servers, and on-prem OAuth providers all live on private networks. If you run Bodhi on a cloud VM with an instance metadata service (IMDS), enforce IMDSv2 at the VM level so a compromised request cannot scrape credentials from 169.254.169.254. This is the cloud platform's job, not the app's.
Threat model in plain language
Bodhi protects against:
- Casual credential theft — leaked API tokens are bounded by SHA-256 hashing and revocation; leaked session cookies are bounded by HttpOnly/Secure flags and ID rotation.
- CSRF — session cookies are
SameSite=Strict, so cross-site form submissions don't carry the user's cookie. - Privilege escalation by lower-tier users — role ceilings stop Managers from operating on Admins; status guards stop replayed approval calls.
- XSS via stored content — CSP plus URL scheme validation block the common stored-script vectors.
- Insider snooping on at-rest data — sensitive credentials in the database are AES-256-GCM-encrypted with a key that lives outside the database.
- Open-proxy abuse — outbound requests are scheme-validated; the forward path uses fixed upstream URLs from the user's API-model record, not arbitrary URLs from request bodies.
Bodhi does not by itself protect against:
- A compromised reverse proxy (TLS, rate limiting, header forwarding).
- A compromised filesystem with both the database file and the encryption key.
- A compromised identity provider (issued tokens are accepted at face value).
- Denial-of-service from very high request rates (handle at the proxy).
- Side-channel attacks on the host (CPU vulnerabilities, container escape).
These are the deployment's responsibility.
Recommended hardening for self-hosters
If you are running Bodhi outside a desktop install, this is the minimum checklist:
- TLS at the proxy. Terminate HTTPS at
nginx/Caddy/your cloud LB. SetBODHI_PUBLIC_SCHEME=httpsso cookies are markedSecure. See Deployment → Reverse Proxy. - Rate limit at the proxy. Tighter limits on
/loginand/ui/auth/*than on chat endpoints. Reject anonymous traffic to/bodhi/v1/*if you don't intend to register external apps. - Set HSTS at the proxy.
Strict-Transport-Security: max-age=31536000; includeSubDomainson every response. - Generate
BODHI_ENCRYPTION_KEYonce, store it securely. Use a 256-bit value fromopenssl rand -base64 32. Back it up where the database is not — losing one without the other should not give an attacker plaintext credentials. - Restrict filesystem access.
$BODHI_HOMEshould be readable only by the user running Bodhi. The HuggingFace cache and the database files in particular should not be world-readable. - Watch the logs. Tail
$BODHI_HOME/logs/for repeated401/403from the same IP. See Observability for log levels and rotation behaviour. - Rotate API tokens. Tokens have no forced expiry; revoke unused ones from the token management page on a schedule. The
last_used_attimestamp helps spot dormant tokens. - Lock down
/dev/*by deployment type. These endpoints are automatically disabled when the app is built for production. If you're running a development build on the public internet, stop.
Where to read more
- Reference → Roles and Scopes — the authoritative role × scope × endpoint matrix.
- Features → API Tokens — token creation, revocation, and best practice.
- Concepts → Auth and Roles — the mental model behind the role hierarchy.
- Deployment → Reverse Proxy — sample TLS, rate limit, and header-forwarding configs.