Reference

Security and encryption

What imbox protects and how, which stored values are encrypted, what is not, and what you must do yourself.

imbox docs

imbox holds your mail and the keys to your mailboxes, so this page says plainly what is protected, how, and where the limits are. Everything here was checked against the code. Where something is not protected, it says so.

To report a vulnerability, use Report a vulnerability on the repository's Security tab on GitHub. Please don't open a public issue.

The short version

QuestionAnswer
Who can use it?One owner. There are no shared accounts.
Where is the data?In a PostgreSQL database on your server. Nothing is sent to outside services unless you turn on a feature that needs one.
How is my password kept?As a salted PBKDF2-SHA256 hash, never the password.
What is encrypted in the database?Mailbox passwords, Gmail and Outlook tokens, AI keys, sign-in app secrets, webhook secrets and two-factor secrets, using AES-256-GCM.
What is not encrypted?Your mail itself, and the disk. See what is not encrypted.
Are backups encrypted?Yes, once you give the server a public key. See Backups.
What do I have to do?Keep SESSION_SECRET, turn on two-factor, harden the server, and protect your backups. See your part.

Signing in

  • Password. Stored as pbkdf2$100000$salt$hash: PBKDF2 with SHA-256, 100,000 rounds and a random salt per password. A password needs at least 8 characters.
  • Lockout. Five wrong passwords within 15 minutes lock that sign-in for 15 minutes. Wrong two-factor codes are counted together with password tries, so the two can't be used to take turns guessing. Six wrong codes close that attempt.
  • Two-factor. An authenticator app (30-second codes) or a passkey. Ten single-use recovery codes are made when you turn it on. They are stored as salted SHA-256 hashes, so imbox can check one but can't show it to you again. The authenticator secret is stored encrypted.
  • Passkeys. Sign in with a fingerprint, face or security key, with no password to steal. imbox keeps only the public part.
  • Sessions. After sign-in, the browser gets a random token in the imbox_session cookie. The cookie is HttpOnly (scripts can't read it), SameSite=Lax and Secure over HTTPS. The database keeps only a hash of the token, so a copy of the database can't be used to sign in. A session lasts 30 days.
  • Changing your password signs out every other browser and device.

What is encrypted

imbox encrypts values it has to be able to read back later, such as a password it must send to your mail server.

Loading diagram...
Stored valueEncrypted
IMAP and SMTP mailbox passwordsYes
Gmail and Outlook access and refresh tokensYes
AI provider keysYes
Google and Microsoft app credentials (client secrets)Yes
Stalwart API keyYes
Agent webhook secretsYes
Authenticator (two-factor) secretYes

Each value is encrypted with a fresh random IV, and AES-GCM also detects any change to the stored bytes. The key comes from SESSION_SECRET, which you set in the server's environment file, not in the database.

What is hashed

Some values are never needed again in their original form, so imbox keeps only a one-way hash:

ValueHow
Account passwordPBKDF2-SHA256, salted
Session tokenSHA-256
Extension token (imx_...)SHA-256
Agent key (hfa_...)A hash. A lost key can't be recovered.
Recovery codesSalted SHA-256

What is not encrypted

  • Your mail. Message text, subjects, contacts and attachment details sit in the database as ordinary rows. This is what keeps search fast: PostgreSQL matches text it can read, so encrypting each field would break search, threading and filters. Protecting these rows is the job of your server's disk and its access controls.
  • The disk. imbox doesn't encrypt the disk. Turn on your provider's disk encryption if you need it.
  • A running server. Encryption in the database doesn't help against someone who gets into the running server, because the app holds SESSION_SECRET in memory to do its work.

Tokens from before they were sealed

Gmail and Outlook tokens used to be stored as they came. New tokens are sealed as they are written, and tokens saved earlier are sealed in the background by the once-a-minute job, a few rows at a time, until none are left. An old backup or database copy from before that may still hold working tokens.

If an old backup or the database leaks

Revoke imbox's access in your Google and Microsoft accounts, change your mailbox passwords, and rotate your AI keys. Without your SESSION_SECRET the encrypted values can't be read, so keep that secret apart from the backups.

Backups

A dump holds all of your mail, so imbox can encrypt each nightly backup with age before it is written. It encrypts to a public key you give it, so the server holds nothing that can open its own backups. The private key stays with you.

  • Without a key set, backups are written as they are. The log says NOT encrypted, and the setup script says so at the end.
  • With a key set, a backup that can't be encrypted fails. It never falls back to a plain file.
  • Files are made readable by root only, written under a temporary name and renamed when complete.

How to set it up and restore is on Backups and restore.

SESSION_SECRET

  • It is made once, at install, with openssl rand -hex 32, and lives in /etc/imbox/imbox.env (mode 600, root) or in .env for Docker.
  • Losing or changing it makes the encrypted values above unreadable. Mailbox passwords, Gmail and Outlook connections, AI keys, client secrets, the Stalwart key and webhook secrets have to be entered or connected again. Two-factor has to be switched off in the database, because its secret can't be decrypted. See Troubleshooting.
  • Not set? If the environment has no SESSION_SECRET, imbox makes one and stores it in the database, so a one-click deploy works. That key then sits beside the data it protects, so a database copy can read it. Set SESSION_SECRET yourself on any server you care about.
  • There is no rotation command. Keep it stable. Copy it somewhere other than the server and your backups.

In transit

  • Browser to imbox. HTTPS, with certificates that Caddy gets and renews. The app sends HSTS, so a browser keeps to HTTPS.
  • imbox to your mail. IMAP on port 993 with TLS, SMTP on 465 (TLS) or 587 (STARTTLS), and HTTPS to Google, Microsoft and AI providers.
  • Between your proxy and the app. Plain HTTP on the same machine, on 127.0.0.1, which never leaves it.

Workflows

A workflow can only do what you can do by hand to one conversation: label it, move it between the Imbox, Feed and Paper Trail, mark it read, set it aside, snooze it, or add it to a collection. It can't delete, send, forward or reply, it never touches mail waiting in the Screener, and it works only on the mailboxes you gave it. The rules use no AI. The one place a model is involved is the optional "AI judges that" condition: it gets the email as untrusted text and can only answer yes or no, so mail can't talk it into a different action. See Workflows.

Reading mail safely

  • Tracking pixels are removed when a message arrives. Other remote images stay hidden until you show them.
  • Message HTML is cleaned, including quoted replies, and shown in a sandboxed frame with its own strict policy: no scripts, no forms, and images only when you allow them. Links open in a new tab and the frame has no referrer.
  • Attachments download rather than open, unless their type is known to be safe to show.
  • Outgoing mail is checked so a recipient name or subject can't smuggle in headers or commands.
  • A sender can be forged. As in any mail program, the address shown is what the message claims.

Protecting the web app

  • Origin check. A request that changes something must come from the app's own address. A page on another site can't use your session.
  • Strict headers. The app sends a content security policy that allows only its own scripts, blocks framing (X-Frame-Options: DENY), turns off the camera, microphone and location, and sends no referrer.
  • Outbound guard. When the server fetches an address someone else chose, such as a calendar feed, a logo, a webhook or a Stalwart server, it refuses names and addresses on your own network. It checks the name first and then the real address at connect time, so a public name that points at a private address is stopped too.
  • Every id is checked. A route that takes an id confirms the row belongs to the signed-in user. Tests cover every route.
  • Narrow doors for outside callers. An extension token reaches seven endpoints. An agent key reaches one mailbox under /agent/v1, and only with the scopes it was given. See Browser extension and Agent mail.

What leaves your server

Nothing is sent to an outside service unless a feature needs it:

WhatWhenWhere
Mail text for AI featuresOnly when you use or switch on a featureThe provider you chose, with your key
Sender pictures and logosWhen mail from a sender arrivesThe sender's domain and image services. Switch it off under Settings → Preferences → Privacy.
A check for new versionsAt most every six hours, from your browserGitHub. Switch it off under Settings → Preferences.
Mail itselfSync and sendingYour own mail providers

The privacy page on the project's website lists every outside address it can contact. See AI assistant for what each AI feature sends.

AI limits

Mail is untrusted text, and a model can be talked into things by it. Once the assistant has read mail in a conversation, the code stops it from sending to people not on that thread, changing many threads without your confirmation, or changing what it remembers about you, whatever the model says. Agents have limits that are enforced in code, not only in their instructions. See Agents in the app.

Your part

imbox can't protect a server that is left open. In order of importance:

  1. Copy SESSION_SECRET to a password manager. Keep it apart from your backups.
  2. Turn on two-factor under Settings → Security, and save the recovery codes.
  3. Harden the server. Run deploy/harden.sh for key-only SSH, fail2ban and automatic security updates. The setup script already opens only SSH, HTTP and HTTPS in the firewall. Limit SSH to your own address at your cloud provider too.
  4. Set APP_URL exactly, with https://. The origin check and cookies depend on it.
  5. Encrypt backups by setting BACKUP_AGE_RECIPIENT, and keep the private key off the server. Copy them somewhere else, in a private bucket. See Backups and restore.
  6. Update. Take new releases. See Updating.
  7. Rotate what expires. Microsoft client secrets expire. See Connect a mailbox.
  8. Keep the database private. The database listens on the same machine only (the script install), or in a private container network (Docker). Don't expose it.

Where each secret lives

SecretWhereProtection
SESSION_SECRETEnvironment file on the serverFile permissions (mode 600, root)
Database passwordDATABASE_URL in the same fileFile permissions
Mailbox passwords, Gmail and Outlook tokens, AI keys, client secrets, webhook secrets, two-factor secretDatabaseAES-256-GCM
Backup private keyWith you, not on the serverYour own care
Account password, session tokens, extension tokens, agent keys, recovery codesDatabaseOne-way hash

Next