Security

Embedding & plans

Security

Mailkaro is server-authoritative: the browser only ever renders what the API grants, and every secret is encrypted at rest. Here's how your keys, sessions, and data are protected.

Secret keys are encrypted at rest#

Server keys (sk_live_…) and publishable keys (pk_live_…) are never stored in plain text. Each is encrypted with AES-256-GCM and indexed by a deterministic HMAC-SHA256 lookup hash, so a key is findable by its value without the database ever holding that value. A server key's full value is shown exactly once, at creation; after that it's masked (sk_live_••••1234).

Sessions & sign-in#

  • Dashboard sessions are HMAC-signed bearer tokens (mk_live_…), verified in constant time, expiring after 24 hours. The database stores only a session id, never the token.
  • One active session per user, a new login or a logout instantly invalidates the previous token.
  • Sign-in is a 6-digit email code that expires in 5 minutes, is attempt-capped, and single-use. No passwords are stored, so there are none to leak.

Embed keys are origin-restricted#

A publishable key can carry a domain allow-list, it resolves only from those origins, on every runtime path, so a leaked pk_live_ key can't be replayed from another site. Server keys are backend-only and never touch a browser.

  • example.com allows that domain and every subdomain.
  • *.vercel.app allows only subdomains, for preview deploys.
  • localhost:5173 allows only that port; an entry without a port allows any port.
  • A pasted URL (https://app.example.com/) is stored as its domain. An entry that is not a domain is refused with 400 invalid_domain.

Server-authoritative entitlements#

The editor's plan and features come from the API at load time, the frontend can't self-grant. Downgrading immediately revokes paid capacity (keys over the cap are deactivated, members over the seat cap are suspended), and the runtime re-checks on every load, so a key can never outlive its plan.

Isolation & encryption in transit / at rest#

  • Database-per-tenant: each workspace gets its own database, one tenant physically cannot read another's data, and every request is membership-checked.
  • In transit: all API and database traffic uses TLS.
  • At rest: the database runs on MongoDB Atlas with encryption at rest, on top of the field-level encryption for secret keys above.
Found a vulnerability? Get in touch, we take security reports seriously and respond quickly.

Frequently asked questions#

How are keys stored?
Server keys and publishable keys are encrypted with AES-256-GCM and found through a deterministic HMAC-SHA256 lookup hash, so the database never holds the plain value. A server key is shown in full once, when it is created.
How does sign-in work?
With a 6-digit email code that expires in 5 minutes, is attempt-capped and works once. No passwords are stored.
Can a leaked publishable key be used from another site?
Not if the key has a domain allow-list. It resolves only from the listed origins, on every runtime path.
Is one customer's data separated from another's?
Yes. Each workspace has its own database, and every request is checked for membership.