Overview
Modelith is infrastructure software that sits on the hot path between your application and the language model. The security model is designed around three commitments:
- Secrets never leave encrypted storage unmasked. API keys, BYOK provider keys, and signing secrets are encrypted at rest and never returned in API responses.
- The hot path is minimal. We do not log prompt content and we do not call the primary database on the routing path; the hot path is Redis-only.
- Abuse is met with bounded blast radius. Rate limits, request body caps, and the billing gate ensure that a single misbehaving key cannot degrade the platform for other customers.
Authentication
Programmatic access uses mlth_ API keys. Keys are generated client-side using secrets.token_urlsafe with 32 bytes of entropy. The full key is shown to the user exactly once at creation. We store only the SHA–256 hash and a short prefix for display in the dashboard; the plaintext key is never persisted.
Dashboard sessions use signed JSON Web Tokens (JWT) with the HS 256 algorithm. Tokens carry a 24-hour expiry and a refresh token with a 30-day expiry. Passwords are hashed with bcrypt at the cost factor configured for the deployment.
Verification tokens (email verification, password reset) are single-use, expire after 24 hours, and are stored as SHA–256 hashes.
BYOK encryption
Provider keys you add to your account are encrypted with Fernet, which uses AES–128 in CBC mode with HMAC–SHA256 authentication. The encryption key is supplied by the ENCRYPTION_KEY environment variable and is rotated on key compromise per the runbook.
Decryption happens only when a routing request must be forwarded to the underlying provider. Decrypted keys are held in process memory for the duration of the request and are not written to logs, not serialized to error reports, and not returned in any API response. The/user-keys endpoint returns a masked key (first 4 + last 4 characters) for identification only.
In production, the application refuses to start if ENCRYPTION_KEY is not set. This is enforced in Settings.validate_production_safety.
Hot-path security
The routing hot path is designed to be as narrow as possible so that the attack surface is small and the audit story is simple:
- No prompt logging. Prompt text and completion text are not written to standard request logs. The default
data_retention_hoursis 0, meaning metadata is purged immediately after the request settles. The only places prompt text is stored, if at all, are the opt-in dashboard surfaces that show the user a clear toggle in settings. - No Postgres on the request path.Routing decisions read from Redis (rate limits, governor state, complexity score) and never call the primary database. The database is consulted only on key validation, which is a SHA–256 hash lookup.
- Tenant isolation. All query paths in the request flow are scoped by user ID or API key ID. Cross-tenant access is rejected at the database query layer.
- Fail-closed on billing. If the billing gate cannot determine whether a request should be served (e.g. Redis is reachable but wallet state is corrupt), the request is rejected with a 402 Payment Required. This is a deliberate inversion of the standard fail-open posture: we never want to mint tokens that we cannot bill for.
Rate limits and body size
Rate limits are enforced on the routing path with a Redis-backed sliding window. Two scopes apply:
- Per API key. The default is 60 requests per 60 seconds, configurable via
RATE_LIMIT_DEFAULT_RPM. A separate per-user limit prevents bypassing the per-key limit by creating many keys. - Per IP.Public endpoints (login, score) are limited to 5–20 requests per 60 seconds per IP to deter credential stuffing and automated abuse.
The HTTP server enforces a 1 MB request body limit on every endpoint (configured in main.py as max_size=1_048_576). Requests larger than 1 MB are rejected with HTTP 413 before any handler runs. The chat file-upload surface has a separate, larger limit (10 MB) configured per feature.
When Redis is unreachable, the rate limiter falls back to an in-memory sliding window. This is a deliberate fail-open on availability: the alternative (rejecting all traffic when Redis is down) would amplify a Redis outage into a full routing outage. The trade-off is logged and surfaced on the status page.
Webhook signature verification
Webhooks delivered to Modelith are signed by the sender. The current critical webhook surface is Paddle billing events, signed with HMAC–SHA 256 via the Paddle-Signature header.
Verification logic (in backend/billing/webhooks.py):
- The raw request body is captured before any JSON parsing.
- The
signatureheader is verified against the HMAC–SHA 256 of the raw body using the shared secret. - Comparison is constant-time via
hmac.compare_digest. - Webhooks with an absent or invalid signature return HTTP 401.
- Each event is deduplicated by event ID to prevent replay.
The webhook secret is loaded from the PADDLE_WEBHOOK_SECRET environment variable. In production, the application logs a warning and rejects all webhooks if the secret is unset.
Audit logs
Security-relevant events are recorded in the audit log (the observability/audit module). The current set of audited actions includes:
- Account registration, login, and password reset
- API key creation and revocation
- Provider key (BYOK) creation, update, and deletion
- Email change, including the requesting IP address
- Routing preference changes
- Admin actions (impersonation, pricing overrides)
Audit entries are write-only from the application code path. Retention is 13 months. Access to the audit log is restricted to operators with the admin role.
Data retention controls
Routing metadata retention is configurable. The global default is set by the data_retention_hours server setting (TRUST–01). Per-user overrides are available on the user record; the permitted range is 0 (purge immediately) to 2,160 hours (90 days).
Customers with strict data-residency or short-retention requirements set the per-user override to 0, which causes routing metadata to be deleted from the operational store as soon as the request settles. This setting is enforced at the scheduler, not at the application layer, so it survives application restart.
Compliance posture
Modelith does not currently hold any third-party security certifications. We are honest about this: SOC 2 Type II, ISO 27001, and HIPAA are on the public roadmap (see the roadmap), but they are not in place today.
What we do have, today, and what we apply to every deployment:
- Encryption in transit (TLS 1.2+ on every public endpoint)
- Encryption at rest for provider keys (Fernet) and the primary database (vendor-managed disk encryption)
- Tenant isolation enforced at the query layer
- Production safety checks that refuse to boot with weak secrets, missing encryption keys, or disabled rate limiting
- Audit logging of security-relevant events
- Webhook signature verification with constant-time comparison
For enterprise customers who require controls beyond what is publicly documented, we offer a custom MSA with the right to audit, a DPA, and contractual commitments on the security baseline. Contact founders@modelith.cloud for the enterprise package.
Vulnerability disclosure
We welcome reports from security researchers. If you have found a vulnerability in the Modelith service, please email security@modelith.cloud with reproduction steps, the impact assessment, and any proof-of-concept code. We commit to the following response targets:
- Acknowledgement within 72 hours on business days
- Initial triage and severity assessment within 5 business days
- Status update at least every 7 business days until the report is resolved
- Coordinated disclosure: we ask that you give us a reasonable window (typically 90 days) to remediate before public disclosure
We do not operate a paid bug bounty program at this stage. We do credit researchers in the public advisory unless anonymity is requested. We do not pursue legal action against good-faith research that complies with this disclosure policy.
Please do not test the production service with real customer data. Use the staging environment and test API keys for any active probing.
Status and uptime
Live status, including component probes for Redis, OpenRouter, the primary database, and billing webhooks, is published at https://status.modelith.cloud. The status page is the authoritative source for the operational state of the service and is updated by the same monitoring that powers the in-product health badge.
Subscribe to status updates by email or RSS to receive incident notifications. Subscribe to security advisories by emailing security@modelith.cloud with the subject line “Subscribe security advisories”.
Roadmap
The following items are on the public security roadmap:
SOC 2 Type II
Bug bounty program
Customer-managed keys
Per-tenant webhook signing
Roadmap items above are commitments, not contractual obligations. Delivery dates may shift as scope and resourcing evolve.