VYPR
High severityNVD Advisory· Published Oct 15, 2024· Updated Apr 15, 2026

CVE-2024-47824

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.

PackageAffected versionsPatched versions
matrix-react-sdknpm
>= 3.18.0, < 3.102.03.102.0

Patches

2
6fc9d7641c51

Remove room key history sharing (#12618)

https://github.com/matrix-org/matrix-react-sdkHubert ChathiJun 14, 2024via ghsa
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

News mentions

0

No linked articles in our index yet.