VYPR
Low severityOSV Advisory· Published Sep 16, 2025· Updated Apr 15, 2026

CVE-2025-59160

CVE-2025-59160

Description

Matrix JavaScript SDK is a Matrix Client-Server SDK for JavaScript and TypeScript. matrix-js-sdk before 38.2.0 has insufficient validation of room predecessor links in MatrixClient::getJoinedRooms, allowing a remote attacker to attempt to replace a tombstoned room with an unrelated attacker-supplied room. The issue has been patched and users should upgrade to 38.2.0. A workaround is to avoid using MatrixClient::getJoinedRooms in favor of getRooms() and filtering upgraded rooms separately.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
matrix-js-sdknpm
< 38.2.038.2.0

Affected products

1

Patches

2
4a9006aea68f

v38.2.0

2 files changed · +7 1
  • CHANGELOG.md+6 0 modified
    @@ -1,3 +1,9 @@
    +Changes in [38.2.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v38.2.0) (2025-09-16)
    +==================================================================================================
    +Fix [CVE-2025-59160](https://www.cve.org/CVERecord?id=CVE-2025-59160) / [GHSA-mp7c-m3rh-r56v](https://github.com/matrix-org/matrix-js-sdk/security/advisories/GHSA-mp7c-m3rh-r56v)
    
    +
    
    +
    +
     Changes in [38.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v38.1.0) (2025-09-09)
     ==================================================================================================
     ## ✨ Features
    
  • package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
         "name": "matrix-js-sdk",
    -    "version": "38.1.0",
    +    "version": "38.2.0",
         "description": "Matrix Client-Server SDK for Javascript",
         "engines": {
             "node": ">=22.0.0"
    
43c72d5bf5e2

Merge commit from fork

https://github.com/matrix-org/matrix-js-sdkMichael TelatynskiSep 16, 2025via ghsa
2 files changed · +30 14
  • spec/unit/matrix-client.spec.ts+23 0 modified
    @@ -2998,6 +2998,8 @@ describe("MatrixClient", function () {
                         replacedByDynamicPredecessor2,
                         room2,
                     ];
    +                client.store.getRoom = (roomId: string) =>
    +                    client.store.getRooms().find((r) => r.roomId === roomId) || null;
                     room1.addLiveEvents(
                         [
                             roomCreateEvent(room1.roomId, replacedByCreate1.roomId),
    @@ -3036,6 +3038,7 @@ describe("MatrixClient", function () {
                         replacedByDynamicPredecessor2,
                     };
                 }
    +
                 it("Returns an empty list if there are no rooms", () => {
                     client.store = new StubStore();
                     client.store.getRooms = () => [];
    @@ -3062,6 +3065,8 @@ describe("MatrixClient", function () {
                     const room2 = new Room("room2", client, "@daryl:alexandria.example.com");
                     client.store = new StubStore();
                     client.store.getRooms = () => [room1, replacedRoom1, replacedRoom2, room2];
    +                client.store.getRoom = (roomId: string) =>
    +                    client.store.getRooms().find((r) => r.roomId === roomId) || null;
                     room1.addLiveEvents([roomCreateEvent(room1.roomId, replacedRoom1.roomId)], { addToState: true });
                     room2.addLiveEvents([roomCreateEvent(room2.roomId, replacedRoom2.roomId)], { addToState: true });
                     replacedRoom1.addLiveEvents([tombstoneEvent(room1.roomId, replacedRoom1.roomId)], { addToState: true });
    @@ -3127,6 +3132,24 @@ describe("MatrixClient", function () {
                     expect(rooms).toContain(room1);
                     expect(rooms).toContain(room2);
                 });
    +
    +            it("should ignore room replacements which are not reciprocated by the predecessor", () => {
    +                const room1 = new Room("room1", client, "@carol:alexandria.example.com");
    +                // Room 2 claims to replace room 1 but room 1 does not agree
    +                const room2 = new Room("replacedRoom1", client, "@daryl:alexandria.example.com");
    +
    +                client.store = new StubStore();
    +                client.store.getRooms = () => [room1, room2];
    +                client.store.getRoom = (roomId: string) =>
    +                    client.store.getRooms().find((r) => r.roomId === roomId) || null;
    +
    +                room2.addLiveEvents([roomCreateEvent(room2.roomId, room1.roomId)], { addToState: true });
    +
    +                // When we ask for the visible rooms
    +                const rooms = client.getVisibleRooms();
    +                expect(rooms).toContain(room1);
    +                expect(rooms).toContain(room2);
    +            });
             });
     
             describe("getRoomUpgradeHistory", () => {
    
  • src/client.ts+7 14 modified
    @@ -2141,21 +2141,14 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
         public getVisibleRooms(msc3946ProcessDynamicPredecessor = false): Room[] {
             const allRooms = this.store.getRooms();
     
    -        const replacedRooms = new Set();
    -        for (const r of allRooms) {
    -            const predecessor = r.findPredecessor(msc3946ProcessDynamicPredecessor)?.roomId;
    -            if (predecessor) {
    -                replacedRooms.add(predecessor);
    +        const visibleRooms = new Set(allRooms);
    +        for (const room of visibleRooms) {
    +            const predecessors = this.findPredecessorRooms(room, true, msc3946ProcessDynamicPredecessor);
    +            for (const predecessor of predecessors) {
    +                visibleRooms.delete(predecessor);
                 }
             }
    -
    -        return allRooms.filter((r) => {
    -            const tombstone = r.currentState.getStateEvents(EventType.RoomTombstone, "");
    -            if (tombstone && replacedRooms.has(r.roomId)) {
    -                return false;
    -            }
    -            return true;
    -        });
    +        return Array.from(visibleRooms);
         }
     
         /**
    @@ -3852,7 +3845,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
             roomId: string,
             includeFuture = true,
         ): Promise<{ [roomId: string]: Error | MatrixError | null }> {
    -        const upgradeHistory = this.getRoomUpgradeHistory(roomId);
    +        const upgradeHistory = this.getRoomUpgradeHistory(roomId, true);
     
             let eligibleToLeave = upgradeHistory;
             if (!includeFuture) {
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.