Skip to content

fix(matrix): replace pickle crypto store with SQLite, fix E2EE decryption - #7981

Merged
alt-glitch merged 1 commit into
mainfrom
fix/matrix-e2ee-crypto-store
Apr 12, 2026
Merged

fix(matrix): replace pickle crypto store with SQLite, fix E2EE decryption#7981
alt-glitch merged 1 commit into
mainfrom
fix/matrix-e2ee-crypto-store

Conversation

@alt-glitch

@alt-glitch alt-glitch commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7952

Summary

Matrix E2EE was completely broken after the mautrix-python migration. The bot could connect but never decrypt messages. Root cause was 6 distinct bugs, all fixed here.

Bugs fixed

Bug 1 — Initial sync drops to-device events

matrix.py:425-444 never called handle_sync() on the initial sync response. Megolm key shares queued while the bot was offline were silently dropped (delivered once by the homeserver, never re-sent).

Bug 2 — Pickle-based crypto store is fragile

MemoryCryptoStore + custom pickle/HMAC persistence tied the serialized format to the exact mautrix version, broke across library migrations (nio → mautrix), and lost all crypto state when the pickle was incompatible.

Fix: Replaced with mautrix's PgCryptoStore backed by SQLite via aiosqlite. This is the same store mautrix bridges use in production. Crypto state auto-persists on every write — no manual serialization.

Bug 3 — No device key verification on startup

After restoring crypto state from disk, hermes never checked that the homeserver still had the correct device keys. A restored account with shared=True whose server-side keys were lost would silently fail.

Fix: Added _verify_device_keys_on_server() — queries /keys/query after olm.load(). Handles: keys missing (re-uploads), stale keys from migration (attempts re-upload with old device deletion), corrupted state (refuses E2EE with actionable error).

Bug 4 — datetime vs float TypeError crashes startup

session.py:suspend_recently_active() compared entry.updated_at (datetime) with time.time() - max_age_seconds (float), raising TypeError: '>=' not supported.

Bug 5 — Redundant share_keys() floods logs

Explicit share_keys() in the sync loop fired on every iteration. OlmMachine already handles this via the DEVICE_OTK_COUNT event handler. The redundant call produced "No one-time keys nor device keys got" warnings 6+ times in 40 seconds.

Bug 6 — MemoryStateStore missing find_shared_rooms (found during E2E testing)

OlmMachine's decryption path calls find_shared_rooms() on the state store, but MemoryStateStore doesn't implement this method. Added _CryptoStateStore adapter that wraps MemoryStateStore and provides the required interface.

Changes

File What
gateway/platforms/matrix.py SQLite crypto store, initial sync dispatch, key verification, CryptoStateStore adapter, removed pickle persistence, removed redundant share_keys
gateway/session.py Fixed datetime/float comparison
pyproject.toml Added aiosqlite>=0.20, asyncpg>=0.29 to [matrix] extra
tests/gateway/test_matrix.py Updated mocks for PgCryptoStore, Database, query_keys, handle_sync
website/docs/.../matrix.md Added E2EE upgrade/migration troubleshooting guide

Breaking changes

Users upgrading from a previous version with MATRIX_ENCRYPTION=true need to:

  1. Generate a new access token (creates a fresh device ID)
  2. Delete ~/.hermes/platforms/matrix/store/crypto_store.pickle and crypto.db
  3. Run /discardsession in Element to force a new Megolm session
  4. Restart the gateway

New installations are unaffected.

Test plan

  • 174 unit tests pass (114 matrix + 60 session)
  • E2E verified: connected to matrix.org with E2EE, sent DM from Element, bot decrypted and responded
  • Verified crypto.db persistence across gateway restarts
  • Verified fresh-install path (no prior state)
  • Verified migration path (stale keys detected, re-uploaded)
  • Verified datetime fix in suspend_recently_active

…tion

Fixes #7952 — Matrix E2EE completely broken after mautrix migration.

- Replace MemoryCryptoStore + pickle/HMAC persistence with mautrix's
  PgCryptoStore backed by SQLite via aiosqlite. Crypto state now
  persists reliably across restarts without fragile serialization.

- Add handle_sync() call on initial sync response so to-device events
  (queued Megolm key shares) are dispatched to OlmMachine instead of
  being silently dropped.

- Add _verify_device_keys_on_server() after loading crypto state.
  Detects missing keys (re-uploads), stale keys from migration
  (attempts re-upload), and corrupted state (refuses E2EE).

- Add _CryptoStateStore adapter wrapping MemoryStateStore to satisfy
  mautrix crypto's StateStore interface (is_encrypted,
  get_encryption_info, find_shared_rooms).

- Remove redundant share_keys() call from sync loop — OlmMachine
  already handles this via DEVICE_OTK_COUNT event handler.

- Fix datetime vs float TypeError in session.py suspend_recently_active()
  that crashed gateway startup.

- Add aiosqlite and asyncpg to [matrix] extra in pyproject.toml.

- Update test mocks for PgCryptoStore/Database and add query_keys mock
  for key verification. 174 tests pass.

- Add E2EE upgrade/migration docs to Matrix user guide.
@m0n5t3r

m0n5t3r commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

confirming it works for me 🎉 (my local qwen3.5 had been pointing at the same pickling thing)

@alt-glitch
alt-glitch merged commit 50d86b3 into main Apr 12, 2026
4 of 7 checks passed
@alt-glitch
alt-glitch deleted the fix/matrix-e2ee-crypto-store branch April 12, 2026 01:54
@cgarwood82

Copy link
Copy Markdown
Contributor

Still borked for me. Is there a way to just nuke the previously configured matrix stuff into the sun and start fresh?

Tommyeds pushed a commit to Tommyeds/hermes-agent that referenced this pull request Apr 12, 2026
…tion (NousResearch#7981)

Fixes NousResearch#7952 — Matrix E2EE completely broken after mautrix migration.

- Replace MemoryCryptoStore + pickle/HMAC persistence with mautrix's
  PgCryptoStore backed by SQLite via aiosqlite. Crypto state now
  persists reliably across restarts without fragile serialization.

- Add handle_sync() call on initial sync response so to-device events
  (queued Megolm key shares) are dispatched to OlmMachine instead of
  being silently dropped.

- Add _verify_device_keys_on_server() after loading crypto state.
  Detects missing keys (re-uploads), stale keys from migration
  (attempts re-upload), and corrupted state (refuses E2EE).

- Add _CryptoStateStore adapter wrapping MemoryStateStore to satisfy
  mautrix crypto's StateStore interface (is_encrypted,
  get_encryption_info, find_shared_rooms).

- Remove redundant share_keys() call from sync loop — OlmMachine
  already handles this via DEVICE_OTK_COUNT event handler.

- Fix datetime vs float TypeError in session.py suspend_recently_active()
  that crashed gateway startup.

- Add aiosqlite and asyncpg to [matrix] extra in pyproject.toml.

- Update test mocks for PgCryptoStore/Database and add query_keys mock
  for key verification. 174 tests pass.

- Add E2EE upgrade/migration docs to Matrix user guide.
aj-nt pushed a commit to aj-nt/hermes-agent that referenced this pull request May 1, 2026
…tion (NousResearch#7981)

Fixes NousResearch#7952 — Matrix E2EE completely broken after mautrix migration.

- Replace MemoryCryptoStore + pickle/HMAC persistence with mautrix's
  PgCryptoStore backed by SQLite via aiosqlite. Crypto state now
  persists reliably across restarts without fragile serialization.

- Add handle_sync() call on initial sync response so to-device events
  (queued Megolm key shares) are dispatched to OlmMachine instead of
  being silently dropped.

- Add _verify_device_keys_on_server() after loading crypto state.
  Detects missing keys (re-uploads), stale keys from migration
  (attempts re-upload), and corrupted state (refuses E2EE).

- Add _CryptoStateStore adapter wrapping MemoryStateStore to satisfy
  mautrix crypto's StateStore interface (is_encrypted,
  get_encryption_info, find_shared_rooms).

- Remove redundant share_keys() call from sync loop — OlmMachine
  already handles this via DEVICE_OTK_COUNT event handler.

- Fix datetime vs float TypeError in session.py suspend_recently_active()
  that crashed gateway startup.

- Add aiosqlite and asyncpg to [matrix] extra in pyproject.toml.

- Update test mocks for PgCryptoStore/Database and add query_keys mock
  for key verification. 174 tests pass.

- Add E2EE upgrade/migration docs to Matrix user guide.
02356abc pushed a commit to 02356abc/hermes-agent that referenced this pull request May 14, 2026
…tion (NousResearch#7981)

Fixes NousResearch#7952 — Matrix E2EE completely broken after mautrix migration.

- Replace MemoryCryptoStore + pickle/HMAC persistence with mautrix's
  PgCryptoStore backed by SQLite via aiosqlite. Crypto state now
  persists reliably across restarts without fragile serialization.

- Add handle_sync() call on initial sync response so to-device events
  (queued Megolm key shares) are dispatched to OlmMachine instead of
  being silently dropped.

- Add _verify_device_keys_on_server() after loading crypto state.
  Detects missing keys (re-uploads), stale keys from migration
  (attempts re-upload), and corrupted state (refuses E2EE).

- Add _CryptoStateStore adapter wrapping MemoryStateStore to satisfy
  mautrix crypto's StateStore interface (is_encrypted,
  get_encryption_info, find_shared_rooms).

- Remove redundant share_keys() call from sync loop — OlmMachine
  already handles this via DEVICE_OTK_COUNT event handler.

- Fix datetime vs float TypeError in session.py suspend_recently_active()
  that crashed gateway startup.

- Add aiosqlite and asyncpg to [matrix] extra in pyproject.toml.

- Update test mocks for PgCryptoStore/Database and add query_keys mock
  for key verification. 174 tests pass.

- Add E2EE upgrade/migration docs to Matrix user guide.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Switch to mautrix-python breaks matrix gateway integration #2

3 participants