IndexedDB Crypto Store#
The IndexedDB crypto store is the primary persistence layer for E2EE (end-to-end encryption) session data in the Matrix JS stack. It holds cryptographic account keys, Olm sessions, inbound Megolm group sessions, device data, and room encryption state — everything required to encrypt and decrypt messages. If this store is lost or inaccessible, the user must re-verify their identity (cross-signing) and loses access to previously received encrypted messages.
Key Objects Stored#
The IndexedDBCryptoStore class (in src/crypto/store/indexeddb-crypto-store.ts) defines the following IndexedDB object stores:
| Store name | Contents |
|---|---|
account | Olm account, migration state |
sessions | Olm to-device sessions |
inbound_group_sessions | Megolm inbound sessions |
inbound_group_sessions_withheld | Withheld session records |
device_data | Tracked device list |
rooms | Per-room encryption config |
sessions_needing_backup | Sessions awaiting backup upload |
In Element Web, the store is instantiated using the database name "matrix-js-sdk:crypto" for the legacy crypto stack , and "matrix-js-sdk::matrix-sdk-crypto" for the Rust crypto stack .
Fallback Hierarchy (legacy crypto stack)#
IndexedDBCryptoStore.startup() attempts to open IndexedDB. On failure it falls back in order:
- IndexedDB — preferred, persistent, full-featured
LocalStorageCryptoStore— if IndexedDB is unavailable or fails the compound-key compatibility test (Edge legacy workaround)MemoryCryptoStore— last resort; data is lost on page reload
A VersionError (database schema is newer than the SDK can handle) short-circuits the fallback and throws InvalidCryptoStoreError instead, because downgrading to a different store would lose existing crypto data .
⚠️ The Rust crypto stack requires IndexedDB — it does not support localStorage or memory fallbacks. If IndexedDB is inaccessible on Rust crypto, the store is marked unhealthy and the client cannot start .
Eviction Risk and Session Loss#
Browsers can silently evict non-persistent IndexedDB storage under disk or memory pressure — a particular risk on mobile devices and in low-storage environments. Because the crypto store lives in IndexedDB, eviction means complete loss of E2EE session state.
Element Web detects this case in checkConsistency(): if localStorage contains data and the mx_crypto_initialised flag is set but the IndexedDB crypto store is empty, it logs:
"Data exists in local storage and crypto is marked as initialised but no data found in crypto store. IndexedDB storage has likely been evicted by the browser!"
This triggers the StorageEvictedDialog — a modal offering the user the option to submit a bug report and sign out . The mx_crypto_initialised flag is persisted to localStorage via setCryptoInitialised() and acts as a sentinel to detect eviction across sessions.
Defense Layers#
Three complementary mechanisms reduce the risk of eviction and its consequences:
1. Persistent storage request at login#
tryPersistStorage() calls navigator.storage.persist() to ask the browser to exclude the origin's IndexedDB from automatic eviction. For Safari, it falls back to document.requestStorageAccess().
This call must happen as early as possible in the login lifecycle. PR #31299 fixed a bug where it was being skipped or racing with login completion — the fix moves the call directly into the Action.OnLoggedIn handler in MatrixChat.tsx so it fires immediately after credentials are established, before any sync or crypto data is written.
PR #33987 (open as of 2026-06) extends tryPersistStorage() to return a Promise<boolean>, short-circuit when already persisted (navigator.storage.persisted()), and call a new warnPersistenceDenied() function if the browser denies the request. The warning is captured by rageshakes and includes a desktop-specific message directing users to enable key backup — because on Electron, there is no main-process API to force per-origin persistence at the OS level.
2. Worker fallback for the sync store#
The sync store (IndexedDBStore) optionally uses a dedicated Web Worker (indexeddbWorkerFactory) to run IndexedDB operations off the main thread. PR #5361 hardened this path: if the worker fails to start (or emits an onerror event during connection), the store now catches the error and automatically falls back to LocalIndexedDBStoreBackend, running IndexedDB directly on the main thread rather than failing entirely.
Note: This worker fallback applies to the sync/session store (
src/store/indexeddb.ts), not the crypto store itself. The crypto store has no worker variant.
3. Post-login consistency check#
checkConsistency() runs on every session restore and login. It validates both the sync store and the crypto store in IndexedDB, and surfaces the StorageEvictedDialog when discrepancies are found. For the Rust crypto stack, both "matrix-js-sdk::matrix-sdk-crypto" (Rust) and "matrix-js-sdk:crypto" (legacy, unmigrated) are checked.
Key Source Files#
| File | Repo | Purpose |
|---|---|---|
src/crypto/store/indexeddb-crypto-store.ts | matrix-js-sdk | IndexedDBCryptoStore class, startup, fallbacks |
src/crypto/store/indexeddb-crypto-store-backend.ts | matrix-js-sdk | IDB backend, schema migrations |
src/crypto/store/base.ts | matrix-js-sdk | CryptoStore interface and data types |
apps/web/src/utils/StorageManager.ts | element-web | tryPersistStorage, checkConsistency, setCryptoInitialised |
apps/web/src/utils/createMatrixClient.ts | element-web | Client creation + crypto store selection |
apps/web/src/components/views/dialogs/StorageEvictedDialog.tsx | element-web | Eviction warning dialog |
src/store/indexeddb.ts | matrix-js-sdk | Sync store with worker fallback (PR #5361) |