fix(MAT-258): blacklist unverified E2EE devices + add CI tests
Some checks failed
Build & Deploy / test (push) Failing after 1m9s
Build & Deploy / build-and-deploy (push) Has been skipped
Tests / test (push) Failing after 8s

Unverified devices (lacking cross-signing) caused OlmUnverifiedDeviceError
in _send_text(), silently breaking all message delivery. Now on_sync()
blacklists non-cross-signed devices instead of skipping them, and
_send_text() catches E2EE errors gracefully.

Adds 12 unit tests for device trust policy and send error handling.
CI test job now gates deployment in deploy.yml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Gick
2026-03-29 08:32:48 +03:00
parent c2985488c4
commit 7b5c157b12
6 changed files with 246 additions and 11 deletions

29
bot.py
View File

@@ -41,6 +41,7 @@ from nio import (
KeyVerificationMac,
ToDeviceError,
)
from nio.exceptions import OlmUnverifiedDeviceError
from nio.events.to_device import UnknownToDeviceEvent
from nio.crypto.attachments import decrypt_attachment
from livekit import api, rtc
@@ -1515,7 +1516,8 @@ class Bot:
self.client.verify_device(device)
logger.info("Cross-sign-verified device %s of %s", device.device_id, user_id)
else:
logger.debug("Skipped unverified device %s of %s (no cross-signing sig)", device.device_id, user_id)
self.client.blacklist_device(device)
logger.info("Blacklisted unverified device %s of %s (no cross-signing sig)", device.device_id, user_id)
async def on_reaction(self, room, event: ReactionEvent):
"""Handle reaction events for pipeline approval flow."""
@@ -3583,16 +3585,21 @@ class Bot:
return None
async def _send_text(self, room_id: str, text: str):
await self.client.room_send(
room_id,
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": text,
"format": "org.matrix.custom.html",
"formatted_body": self._md_to_html(text),
},
)
try:
await self.client.room_send(
room_id,
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": text,
"format": "org.matrix.custom.html",
"formatted_body": self._md_to_html(text),
},
)
except OlmUnverifiedDeviceError as e:
logger.error("E2EE send failed in room %s: unverified device — %s", room_id, e)
except Exception as e:
logger.error("Send failed in room %s: %s", room_id, e)
async def _get_call_encryption_key(self, room_id: str, sender: str, caller_device_id: str = "") -> bytes | None:
"""Read E2EE encryption key from call.member state (MSC4143) or timeline (legacy).