matrix-appservice-irc events can be crafted to leak parts of targeted messages from other bridged rooms
Description
matrix-appservice-irc is a Node.js IRC bridge for Matrix. Prior to version 1.0.1, it was possible to craft an event such that it would leak part of a targeted message event from another bridged room. This required knowing an event ID to target. Version 1.0.1n fixes this issue. As a workaround, set the matrixHandler.eventCacheSize config value to 0. This workaround may impact performance.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Matrix IRC bridge prior to 1.0.1 leaks message content from other rooms via crafted events.
Vulnerability
Overview
CVE-2023-38700 is a vulnerability in matrix-appservice-irc, a Node.js IRC bridge for Matrix, that allows an attacker to leak part of a targeted message event from another bridged room. The root cause is that the event cache only indexed events by their event ID, without considering the room ID. This made it possible to craft an event (such as an edit) that references a known event ID from a different room, causing the bridge to retrieve the cached message body from that room and include it in a response [1][2].
Exploitation
To exploit, an attacker must know the event ID of a target message in another bridged room. By sending a crafted event (e.g., an edit with m.relates_to pointing to that event ID), the bridge's getCachedEvent method returns the cached body from any room, as long as the event ID exists in the cache. No authentication is required beyond being a user in some bridged room, and the attacker can be in a different room than the target [2].
Impact
Successful exploitation results in the partial disclosure of the targeted message's body content. This leak is limited to the cached body snippet (not the full event) and requires the target event to have been recently processed so it remains in the cache. The vulnerability does not allow arbitrary data access but can reveal sensitive information from other bridged conversations [1].
Mitigation
The issue is fixed in version 1.0.1, which modifies the cache to include the room ID as part of the key [2][4]. Users unable to upgrade can set matrixHandler.eventCacheSize to 0 in the configuration, disabling the event cache entirely, though this may impact performance [1]. The release notes for 1.0.1 (2023-07-31) strongly recommend upgrading [4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
matrix-appservice-ircnpm | < 1.0.1 | 1.0.1 |
Affected products
2- matrix-org/matrix-appservice-ircv5Range: < 1.0.1
Patches
18bbd2b69a16cMerge pull request from GHSA-c7hh-3v6c-fj4q
1 file changed · +26 −19
src/bridge/MatrixHandler.ts+26 −19 modified@@ -119,16 +119,17 @@ interface CachedEvent { } export class MatrixHandler { - private readonly processingInvitesForRooms: { - [roomIdUserId: string]: Promise<unknown>; - } = {}; // maintain a list of room IDs which are being processed invite-wise. This is // required because invites are processed asyncly, so you could get invite->msg // and the message is processed before the room is created. + private readonly processingInvitesForRooms: { + [roomIdUserId: string]: Promise<unknown>; + } = {}; + // Map of `roomId-eventId` -> cached event private readonly eventCache: Map<string, CachedEvent> = new Map(); private readonly metrics: {[domain: string]: { - [metricName: string]: number; - };} = {}; + [metricName: string]: number; + };} = {}; private readonly mediaUrl: string; private memberTracker?: StateLookup; private adminHandler: AdminRoomHandler; @@ -1048,7 +1049,7 @@ export class MatrixHandler { // special handling for edits if (event.content["m.relates_to"]?.rel_type === "m.replace") { const originalEventId = event.content["m.relates_to"].event_id; - let originalBody = this.getCachedEvent(originalEventId)?.body; + let originalBody = this.getCachedEvent(event.room_id, originalEventId)?.body; if (!originalBody) { try { // FIXME: this will return the new event rather than the original one @@ -1087,11 +1088,15 @@ export class MatrixHandler { body = body.substring(0, nextNewLine); } // Cache events in here so we can refer to them for replies. - this.cacheEvent(event.event_id, { - body: cacheBody, - sender: event.sender, - timestamp: event.origin_server_ts, - }); + this.cacheEvent( + event.room_id, + event.event_id, + { + body: cacheBody, + sender: event.sender, + timestamp: event.origin_server_ts, + }, + ); // The client might still be connected, for abundance of safety let's wait. await ircClient.waitForConnected(); @@ -1268,20 +1273,20 @@ export class MatrixHandler { }; const REPLY_NAME_MAX_LENGTH = 12; - const eventId = replyEventId; if (!event.content.body) { return null; } const rplText = replyText(event.content.body); let rplName: string; let rplSource: string; - let cachedEvent = this.getCachedEvent(eventId); + // Reply must be in the same room as the original event. + let cachedEvent = this.getCachedEvent(event.room_id, replyEventId); if (!cachedEvent) { // Fallback to fetching from the homeserver. try { const eventContent = await this.ircBridge.getAppServiceBridge().getIntent().getEvent( - event.room_id, eventId + event.room_id, replyEventId ); rplName = eventContent.sender; if (typeof(eventContent.content.body) !== "string") { @@ -1296,7 +1301,7 @@ export class MatrixHandler { rplSource = eventContent.content.body; } cachedEvent = {sender: rplName, body: rplSource, timestamp: eventContent.origin_server_ts}; - this.cacheEvent(eventId, cachedEvent); + this.cacheEvent(eventContent.room_id, eventContent.event_id, cachedEvent); } catch (err) { // If we couldn't find the event, then frankly we can't @@ -1375,17 +1380,19 @@ export class MatrixHandler { this.metrics[serverDomain] = metricSet; } - private cacheEvent(id: string, event: CachedEvent) { - this.eventCache.set(id, event); + private cacheEvent(roomId: string, eventId: string, event: CachedEvent) { + const cacheKey = `${roomId}-${eventId}`; + this.eventCache.set(cacheKey, event); if (this.eventCache.size > this.config.eventCacheSize) { const delKey = this.eventCache.entries().next().value[0]; this.eventCache.delete(delKey); } } - private getCachedEvent(id: string): CachedEvent|undefined { - return this.eventCache.get(id); + private getCachedEvent(roomId: string, eventId: string): CachedEvent|undefined { + const cacheKey = `${roomId}-${eventId}`; + return this.eventCache.get(cacheKey); } // EXPORTS
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-c7hh-3v6c-fj4qghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-38700ghsaADVISORY
- github.com/matrix-org/matrix-appservice-irc/commit/8bbd2b69a16cbcbeffdd9b5c973fd89d61498d75ghsax_refsource_MISCWEB
- github.com/matrix-org/matrix-appservice-irc/releases/tag/1.0.1ghsax_refsource_MISCWEB
- github.com/matrix-org/matrix-appservice-irc/security/advisories/GHSA-c7hh-3v6c-fj4qghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.