Files
hermes-agent/tests/gateway/test_matrix_media_gate_before_download.py
teknium1 7f13d3f43d fix(matrix): gate require_mention / allowed rooms before downloading inbound media
`_handle_media_message` fetched the mxc:// payload (`_download_and_cache_media`)
before `_build_inbound_event` ran `_resolve_message_context`, so an unmentioned
or non-allowlisted room's m.image/m.file was pulled onto the host and then
dropped. Resolve the context first and hand it to `_build_inbound_event` via a
new `ctx` kwarg so the read receipt / thread mark are still applied once.

Same class as the Teams fix in this PR (review follow-up). One invariant test:
unmentioned group media → 0 downloads, mentioned → 1.
2026-09-18 09:55:55 -07:00

52 lines
2.2 KiB
Python

"""Matrix must gate (require_mention / allowed rooms) BEFORE downloading inbound media.
An unmentioned ``m.image`` in a gated group room used to be fetched from the homeserver
(``_download_and_cache_media``) and only then dropped by ``_resolve_message_context``.
The invariant: a dropped media event performs zero downloads; a mentioned one still does.
"""
import time
from types import SimpleNamespace
from unittest.mock import AsyncMock
import pytest
def _make_adapter(monkeypatch):
monkeypatch.setenv("MATRIX_REQUIRE_MENTION", "true")
monkeypatch.setenv("MATRIX_AUTO_THREAD", "false")
from gateway.config import PlatformConfig
from plugins.platforms.matrix.adapter import MatrixAdapter
adapter = MatrixAdapter(PlatformConfig(
enabled=True, token="syt_test_token",
extra={"homeserver": "https://matrix.example.org", "user_id": "@hermes:example.org"}))
adapter._startup_ts = time.time() - 10
adapter.handle_message = AsyncMock()
adapter._client = None
adapter._resolve_room_identity = AsyncMock(return_value=SimpleNamespace(
display_name="Group Room", room_topic=None, server_name="example.org", chat_type="group"))
adapter._is_dm_room = AsyncMock(return_value=False)
adapter._download_and_cache_media = AsyncMock(return_value="/tmp/cached.png")
return adapter
def _image_event(body):
return SimpleNamespace(
sender="@alice:example.org", event_id="$img1", room_id="!group:example.org",
timestamp=int(time.time() * 1000),
content={"body": body, "msgtype": "m.image", "url": "mxc://example.org/abc",
"info": {"mimetype": "image/png", "size": 1024}})
@pytest.mark.asyncio
@pytest.mark.parametrize("body, downloads, dispatched", [
("photo.png", 0, 0), # unmentioned group media: never fetched
("@hermes:example.org look", 1, 1), # mentioned: fetched and dispatched
])
async def test_unmentioned_group_media_is_not_downloaded(monkeypatch, body, downloads, dispatched):
adapter = _make_adapter(monkeypatch)
await adapter._on_room_message(_image_event(body))
assert adapter._download_and_cache_media.await_count == downloads
assert adapter.handle_message.await_count == dispatched