diff --git a/CHANGELOG.md b/CHANGELOG.md index 961fa8f791d..9b4264fc1bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +Changes in [34.8.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.8.0) (2024-10-15) +================================================================================================== +This release removes insecure functionality, resolving CVE-2024-47080 / GHSA-4jf8-g8wp-cx7c. + Changes in [34.7.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v34.7.0) (2024-10-08) ================================================================================================== ## 🦖 Deprecations diff --git a/package.json b/package.json index 8e83aee7beb..421da62f7d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-js-sdk", - "version": "34.7.0", + "version": "34.8.0", "description": "Matrix Client-Server SDK for Javascript", "engines": { "node": ">=20.0.0" diff --git a/spec/unit/models/MSC3089TreeSpace.spec.ts b/spec/unit/models/MSC3089TreeSpace.spec.ts index 5a63c282c5a..2a07ced56cc 100644 --- a/spec/unit/models/MSC3089TreeSpace.spec.ts +++ b/spec/unit/models/MSC3089TreeSpace.spec.ts @@ -107,7 +107,7 @@ describe("MSC3089TreeSpace", () => { return Promise.resolve(); }); client.invite = fn; - await tree.invite(target, false, false); + await tree.invite(target, false); expect(fn).toHaveBeenCalledTimes(1); }); @@ -120,7 +120,7 @@ describe("MSC3089TreeSpace", () => { return Promise.resolve(); }); client.invite = fn; - await tree.invite(target, false, false); + await tree.invite(target, false); expect(fn).toHaveBeenCalledTimes(2); }); @@ -133,7 +133,7 @@ describe("MSC3089TreeSpace", () => { }); client.invite = fn; - await expect(tree.invite(target, false, false)).rejects.toThrow("MatrixError: Sample Failure"); + await expect(tree.invite(target, false)).rejects.toThrow("MatrixError: Sample Failure"); expect(fn).toHaveBeenCalledTimes(1); }); @@ -155,61 +155,10 @@ describe("MSC3089TreeSpace", () => { { invite: (userId) => fn(tree.roomId, userId) } as MSC3089TreeSpace, ]; - await tree.invite(target, true, false); + await tree.invite(target, true); expect(fn).toHaveBeenCalledTimes(4); }); - it("should share keys with invitees", async () => { - const target = targetUser; - const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => { - expect(inviteRoomId).toEqual(roomId); - expect(userIds).toMatchObject([target]); - return Promise.resolve(); - }); - client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests - client.sendSharedHistoryKeys = sendKeysFn; - - // Mock the history check as best as possible - const historyVis = "shared"; - const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => { - // We're not expecting a super rigid test: the function that calls this internally isn't - // really being tested here. - expect(eventType).toEqual(EventType.RoomHistoryVisibility); - expect(stateKey).toEqual(""); - return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase - }); - room.currentState.getStateEvents = historyFn; - - // Note: inverse test is implicit from other tests, which disable the call stack of this - // test in order to pass. - await tree.invite(target, false, true); - expect(sendKeysFn).toHaveBeenCalledTimes(1); - expect(historyFn).toHaveBeenCalledTimes(1); - }); - - it("should not share keys with invitees if inappropriate history visibility", async () => { - const target = targetUser; - const sendKeysFn = jest.fn().mockImplementation((inviteRoomId: string, userIds: string[]) => { - expect(inviteRoomId).toEqual(roomId); - expect(userIds).toMatchObject([target]); - return Promise.resolve(); - }); - client.invite = () => Promise.resolve({}); // we're not testing this here - see other tests - client.sendSharedHistoryKeys = sendKeysFn; - - const historyVis = "joined"; // NOTE: Changed. - const historyFn = jest.fn().mockImplementation((eventType: string, stateKey?: string) => { - expect(eventType).toEqual(EventType.RoomHistoryVisibility); - expect(stateKey).toEqual(""); - return { getContent: () => ({ history_visibility: historyVis }) }; // eslint-disable-line camelcase - }); - room.currentState.getStateEvents = historyFn; - - await tree.invite(target, false, true); - expect(sendKeysFn).toHaveBeenCalledTimes(0); - expect(historyFn).toHaveBeenCalledTimes(1); - }); - async function evaluatePowerLevels(pls: any, role: TreePermissions, expectedPl: number) { makePowerLevels(pls); const fn = jest diff --git a/src/client.ts b/src/client.ts index 514586047a8..4059e68e63c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -4085,43 +4085,6 @@ export class MatrixClient extends TypedEventEmitter { - if (!this.crypto) { - throw new Error("End-to-end encryption disabled"); - } - - const roomEncryption = this.crypto?.getRoomEncryption(roomId); - if (!roomEncryption) { - // unknown room, or unencrypted room - this.logger.error("Unknown room. Not sharing decryption keys"); - return; - } - - const deviceInfos = await this.crypto.downloadKeys(userIds); - const devicesByUser: Map = new Map(); - for (const [userId, devices] of deviceInfos) { - devicesByUser.set(userId, Array.from(devices.values())); - } - - // XXX: Private member access - const alg = this.crypto.getRoomDecryptor(roomId, roomEncryption.algorithm); - if (alg.sendSharedHistoryInboundSessions) { - await alg.sendSharedHistoryInboundSessions(devicesByUser); - } else { - this.logger.warn("Algorithm does not support sharing previous keys", roomEncryption.algorithm); - } - } - /** * Get the config for the media repository. * @returns Promise which resolves with an object containing the config. diff --git a/src/models/MSC3089TreeSpace.ts b/src/models/MSC3089TreeSpace.ts index 8bcc933d7c2..6de3a05fd50 100644 --- a/src/models/MSC3089TreeSpace.ts +++ b/src/models/MSC3089TreeSpace.ts @@ -30,7 +30,6 @@ import { simpleRetryOperation, } from "../utils.ts"; import { MSC3089Branch } from "./MSC3089Branch.ts"; -import { isRoomSharedHistory } from "../crypto/algorithms/megolm.ts"; import { ISendEventResponse } from "../@types/requests.ts"; import { FileType } from "../http-api/index.ts"; import { KnownMembership } from "../@types/membership.ts"; @@ -136,28 +135,14 @@ export class MSC3089TreeSpace { * @param userId - The user ID to invite. * @param andSubspaces - True (default) to invite the user to all * directories/subspaces too, recursively. - * @param shareHistoryKeys - True (default) to share encryption keys - * with the invited user. This will allow them to decrypt the events (files) - * in the tree. Keys will not be shared if the room is lacking appropriate - * history visibility (by default, history visibility is "shared" in trees, - * which is an appropriate visibility for these purposes). * @returns Promise which resolves when complete. */ - public async invite(userId: string, andSubspaces = true, shareHistoryKeys = true): Promise { + public async invite(userId: string, andSubspaces = true): Promise { const promises: Promise[] = [this.retryInvite(userId)]; if (andSubspaces) { - promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces, shareHistoryKeys))); + promises.push(...this.getDirectories().map((d) => d.invite(userId, andSubspaces))); } - return Promise.all(promises).then(() => { - // Note: key sharing is default on because for file trees it is relatively important that the invite - // target can actually decrypt the files. The implied use case is that by inviting a user to the tree - // it means the sender would like the receiver to view/download the files contained within, much like - // sharing a folder in other circles. - if (shareHistoryKeys && isRoomSharedHistory(this.room)) { - // noinspection JSIgnoredPromiseFromCall - we aren't concerned as much if this fails. - this.client.sendSharedHistoryKeys(this.roomId, [userId]); - } - }); + await Promise.all(promises); } private retryInvite(userId: string): Promise {