VYPR
Low severityNVD Advisory· Published Aug 4, 2023· Updated Oct 3, 2024

matrix-appservice-irc events can be crafted to leak parts of targeted messages from other bridged rooms

CVE-2023-38700

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.

PackageAffected versionsPatched versions
matrix-appservice-ircnpm
< 1.0.11.0.1

Affected products

2

Patches

1
8bbd2b69a16c

Merge pull request from GHSA-c7hh-3v6c-fj4q

https://github.com/matrix-org/matrix-appservice-ircJustin CarlsonJul 31, 2023via ghsa
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

News mentions

0

No linked articles in our index yet.