CVE-2024-47824
Description
matrix-react-sdk is react-based software development kit for inserting a Matrix chat/VOIP client into a web page. Starting in version 3.18.0 and before 3.102.0, matrix-react-sdk allows a malicious homeserver to potentially steal message keys for a room when a user invites another user to that room, via injection of a malicious device controlled by the homeserver. This is possible because matrix-react-sdk before 3.102.0 shared historical message keys on invite. Version 3.102.0 fixes this issue by disabling sharing message keys on invite by removing calls to the vulnerable functionality. No known workarounds are available.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
matrix-react-sdknpm | >= 3.18.0, < 3.102.0 | 3.102.0 |
Patches
22bf3335557656fc9d7641c51Remove room key history sharing (#12618)
6 files changed · +8 −57
src/components/views/dialogs/InviteDialog.tsx+1 −19 modified@@ -22,7 +22,6 @@ import { MatrixCall } from "matrix-js-sdk/src/webrtc/call"; import { logger } from "matrix-js-sdk/src/logger"; import { uniqBy } from "lodash"; -import { Icon as InfoIcon } from "../../../../res/img/element-icons/info.svg"; import { Icon as EmailPillAvatarIcon } from "../../../../res/img/icon-email-pill-avatar.svg"; import { _t, _td } from "../../../languageHandler"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; @@ -624,7 +623,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial } try { - const result = await inviteMultipleToRoom(cli, this.props.roomId, targetIds, true); + const result = await inviteMultipleToRoom(cli, this.props.roomId, targetIds); if (!this.shouldAbortAfterInviteError(result, room)) { // handles setting error message too this.props.onFinished(true); @@ -1279,7 +1278,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial let consultConnectSection; let extraSection; let footer; - let keySharingWarning = <span />; const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer); @@ -1391,21 +1389,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial buttonText = _t("action|invite"); goButtonFn = this.inviteUsers; - - if (cli.isRoomEncrypted(this.props.roomId)) { - const room = cli.getRoom(this.props.roomId)!; - const visibilityEvent = room.currentState.getStateEvents("m.room.history_visibility", ""); - const visibility = - visibilityEvent && visibilityEvent.getContent() && visibilityEvent.getContent().history_visibility; - if (visibility === "world_readable" || visibility === "shared") { - keySharingWarning = ( - <p className="mx_InviteDialog_helpText"> - <InfoIcon height={14} width={14} /> - {" " + _t("invite|key_share_warning")} - </p> - ); - } - } } else if (this.props.kind === InviteKind.CallTransfer) { title = _t("action|transfer"); @@ -1471,7 +1454,6 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial {spinner} </div> </div> - {keySharingWarning} {this.renderIdentityServerWarning()} <div className="error">{this.state.errorText}</div> {onlyOneThreepidNote}
src/i18n/strings/en_EN.json+0 −1 modified@@ -1294,7 +1294,6 @@ "failed_generic": "Operation failed", "failed_title": "Failed to invite", "invalid_address": "Unrecognised address", - "key_share_warning": "Invited people will be able to read old messages.", "name_email_mxid_share_room": "Invite someone using their name, email address, username (like <userId/>) or <a>share this room</a>.", "name_email_mxid_share_space": "Invite someone using their name, email address, username (like <userId/>) or <a>share this space</a>.", "name_mxid_share_room": "Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
src/RoomInvite.tsx+2 −7 modified@@ -40,21 +40,17 @@ export interface IInviteResult { * * @param {string} roomId The ID of the room to invite to * @param {string[]} addresses Array of strings of addresses to invite. May be matrix IDs or 3pids. - * @param {boolean} sendSharedHistoryKeys whether to share e2ee keys with the invitees if applicable. * @param {function} progressCallback optional callback, fired after each invite. * @returns {Promise} Promise */ export function inviteMultipleToRoom( client: MatrixClient, roomId: string, addresses: string[], - sendSharedHistoryKeys = false, progressCallback?: () => void, ): Promise<IInviteResult> { const inviter = new MultiInviter(client, roomId, progressCallback); - return inviter - .invite(addresses, undefined, sendSharedHistoryKeys) - .then((states) => Promise.resolve({ states, inviter })); + return inviter.invite(addresses, undefined).then((states) => Promise.resolve({ states, inviter })); } export function showStartChatInviteDialog(initialText = ""): void { @@ -105,10 +101,9 @@ export function inviteUsersToRoom( client: MatrixClient, roomId: string, userIds: string[], - sendSharedHistoryKeys = false, progressCallback?: () => void, ): Promise<void> { - return inviteMultipleToRoom(client, roomId, userIds, sendSharedHistoryKeys, progressCallback) + return inviteMultipleToRoom(client, roomId, userIds, progressCallback) .then((result) => { const room = client.getRoom(roomId)!; showAnyInviteErrors(result.states, room, result.inviter);
src/SlashCommands.tsx+1 −1 modified@@ -420,7 +420,7 @@ export const Commands = [ return success( prom .then(() => { - return inviter.invite([address], reason, true); + return inviter.invite([address], reason); }) .then(() => { if (inviter.getCompletionState(address) !== "invited") {
src/utils/MultiInviter.ts+3 −28 modified@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { MatrixError, MatrixClient, EventType, HistoryVisibility } from "matrix-js-sdk/src/matrix"; +import { MatrixError, MatrixClient, EventType } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { defer, IDeferred } from "matrix-js-sdk/src/utils"; import { logger } from "matrix-js-sdk/src/logger"; @@ -83,10 +83,9 @@ export default class MultiInviter { * * @param {array} addresses Array of addresses to invite * @param {string} reason Reason for inviting (optional) - * @param {boolean} sendSharedHistoryKeys whether to share e2ee keys with the invitees if applicable. * @returns {Promise} Resolved when all invitations in the queue are complete */ - public invite(addresses: string[], reason?: string, sendSharedHistoryKeys = false): Promise<CompletionStates> { + public invite(addresses: string[], reason?: string): Promise<CompletionStates> { if (this.addresses.length > 0) { throw new Error("Already inviting/invited"); } @@ -105,31 +104,7 @@ export default class MultiInviter { this.deferred = defer<CompletionStates>(); this.inviteMore(0); - if (!sendSharedHistoryKeys || !this.roomId || !this.matrixClient.isRoomEncrypted(this.roomId)) { - return this.deferred.promise; - } - - const room = this.matrixClient.getRoom(this.roomId); - const visibilityEvent = room?.currentState.getStateEvents(EventType.RoomHistoryVisibility, ""); - const visibility = visibilityEvent?.getContent().history_visibility; - - if (visibility !== HistoryVisibility.WorldReadable && visibility !== HistoryVisibility.Shared) { - return this.deferred.promise; - } - - return this.deferred.promise.then(async (states): Promise<CompletionStates> => { - const invitedUsers: string[] = []; - for (const [addr, state] of Object.entries(states)) { - if (state === InviteState.Invited && getAddressType(addr) === AddressType.MatrixUserId) { - invitedUsers.push(addr); - } - } - - logger.log("Sharing history with", invitedUsers); - this.matrixClient.sendSharedHistoryKeys(this.roomId, invitedUsers); // do this in the background - - return states; - }); + return this.deferred.promise; } /**
src/utils/RoomUpgrade.ts+1 −1 modified@@ -120,7 +120,7 @@ export async function upgradeRoom( if (toInvite.length > 0) { // Errors are handled internally to this function - await inviteUsersToRoom(cli, newRoomId, toInvite, false, () => { + await inviteUsersToRoom(cli, newRoomId, toInvite, () => { progress.inviteUsersProgress!++; progressCallback?.(progress); });
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
5- github.com/advisories/GHSA-qcvh-p9jq-wp8vghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-47824ghsaADVISORY
- github.com/matrix-org/matrix-react-sdk/commit/6fc9d7641c51ca3db8225cf58b9d6e6fdd2d6556nvdWEB
- github.com/matrix-org/matrix-react-sdk/pull/12618nvdWEB
- github.com/matrix-org/matrix-react-sdk/security/advisories/GHSA-qcvh-p9jq-wp8vnvdWEB
News mentions
0No linked articles in our index yet.