Skip to content

feat: voice to video escalation - #40983

Merged
pierre-lehnen-rc merged 18 commits into
feat/voice-pocfrom
dmv/voice-to-video-escalation
Jul 14, 2026
Merged

feat: voice to video escalation#40983
pierre-lehnen-rc merged 18 commits into
feat/voice-pocfrom
dmv/voice-to-video-escalation

Conversation

@pierre-lehnen-rc

@pierre-lehnen-rc pierre-lehnen-rc commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Proposed changes (including videos or screenshots)

DMV-17

Issue(s)

Steps to test or reproduce

Further comments

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added the ability to escalate ongoing voice calls into video conferences.
    • Added video-call actions, confirmation prompts, escalation status indicators, and automatic transition messaging.
    • Added Pexip configuration options for SIP, persistent chat, aliases, and escalation behavior.
    • Added support for opening video calls in desktop windows or browser popups.
    • Added a clear warning when browser popups are blocked.
  • Bug Fixes
    • Improved call ending, transfer, screen-sharing, and escalation handling for more consistent behavior.

@pierre-lehnen-rc pierre-lehnen-rc added this to the POC-VOICE milestone Jun 16, 2026
@dionisio-bot

dionisio-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a761ff2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@rocket.chat/meteor Minor
@rocket.chat/core-typings Minor
@rocket.chat/model-typings Minor
@rocket.chat/models Minor
@rocket.chat/apps Minor
@rocket.chat/media-calls Minor
@rocket.chat/ui-voip Major
@rocket.chat/i18n Minor
@rocket.chat/rest-typings Minor
@rocket.chat/license Minor
@rocket.chat/desktop-api Minor
@rocket.chat/apps-engine Minor
@rocket.chat/ui-client Major
@rocket.chat/mock-providers Patch
@rocket.chat/ui-contexts Major
@rocket.chat/web-ui-registration Major
@rocket.chat/uikit-playground Patch
@rocket.chat/fuselage-ui-kit Major
@rocket.chat/gazzodown Major
@rocket.chat/livechat Patch
@rocket.chat/ui-avatar Major
@rocket.chat/ui-video-conf Major
@rocket.chat/ui-composer Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a326780c-2a7b-4514-9861-f8daaef6f3cc

📥 Commits

Reviewing files that changed from the base of the PR and between 6f91abc and a761ff2.

⛔ Files ignored due to path filters (3)
  • packages/ui-voip/src/views/ConfirmVideoEscalationModal/__snapshots__/ConfirmVideoEscalationModal.spec.tsx.snap is excluded by !**/*.snap
  • packages/ui-voip/src/views/PopupBlockedModal/__snapshots__/PopupBlockedModal.spec.tsx.snap is excluded by !**/*.snap
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (67)
  • apps/meteor/ee/server/settings/voip.ts
  • apps/meteor/server/api/v1/media-calls.ts
  • apps/meteor/server/services/media-call/service.ts
  • apps/meteor/server/services/video-conference/service.ts
  • apps/meteor/server/settings/pexip.ts
  • ee/packages/media-calls/src/constants.ts
  • ee/packages/media-calls/src/definition/IMediaCallServer.ts
  • ee/packages/media-calls/src/index.ts
  • ee/packages/media-calls/src/internal/agents/UserActorAgent.ts
  • ee/packages/media-calls/src/server/CallDirector.ts
  • ee/packages/media-calls/src/server/MediaCallServer.ts
  • ee/packages/media-calls/src/server/getDefaultSettings.ts
  • ee/packages/media-calls/src/sip/Session.ts
  • ee/packages/media-calls/src/sip/providers/BaseSipCall.ts
  • ee/packages/media-calls/src/sip/providers/IncomingSipCall.ts
  • ee/packages/media-calls/src/sip/providers/OutgoingSipCall.ts
  • packages/core-services/src/types/IMediaCallService.ts
  • packages/core-services/src/types/IVideoConfService.ts
  • packages/core-typings/src/IVideoConference.ts
  • packages/core-typings/src/mediaCalls/IMediaCall.ts
  • packages/i18n/src/locales/de.i18n.json
  • packages/i18n/src/locales/en.i18n.json
  • packages/media-signaling/src/definition/call/CallEvents.ts
  • packages/media-signaling/src/definition/call/IClientMediaCall.ts
  • packages/media-signaling/src/definition/call/callStates/IDirectMediaCallData.ts
  • packages/media-signaling/src/definition/signals/server/notification.ts
  • packages/media-signaling/src/lib/Call.ts
  • packages/media-signaling/src/lib/Session.ts
  • packages/model-typings/src/models/IMediaCallsModel.ts
  • packages/model-typings/src/models/IVideoConferenceModel.ts
  • packages/models/src/models/MediaCalls.ts
  • packages/models/src/models/VideoConference.ts
  • packages/pexip/package.json
  • packages/pexip/src/definition/PexipSettings.ts
  • packages/pexip/src/endpoints/endpoint.ts
  • packages/pexip/src/endpoints/eventSink.ts
  • packages/pexip/src/endpoints/serviceConfiguration.ts
  • packages/pexip/src/videoConfProvider.ts
  • packages/rest-typings/src/index.ts
  • packages/rest-typings/src/v1/media-calls.ts
  • packages/ui-voip/src/components/Actions/ActionStrip.tsx
  • packages/ui-voip/src/components/VideoCallButton.stories.tsx
  • packages/ui-voip/src/components/VideoCallButton.tsx
  • packages/ui-voip/src/components/VideoCallWidgetAction.stories.tsx
  • packages/ui-voip/src/components/VideoCallWidgetAction.tsx
  • packages/ui-voip/src/components/index.ts
  • packages/ui-voip/src/context/MediaCallViewContext.ts
  • packages/ui-voip/src/context/definitions.d.ts
  • packages/ui-voip/src/hooks/useOpenVideoCall.spec.tsx
  • packages/ui-voip/src/hooks/useOpenVideoCall.tsx
  • packages/ui-voip/src/hooks/useVoiceToVideoEscalation.tsx
  • packages/ui-voip/src/providers/MediaCallViewProvider.tsx
  • packages/ui-voip/src/providers/MockedMediaCallProvider.tsx
  • packages/ui-voip/src/providers/useMediaSession.ts
  • packages/ui-voip/src/providers/useMediaSessionInstance.ts
  • packages/ui-voip/src/views/ConfirmVideoEscalationModal/ConfirmVideoEscalationModal.spec.tsx
  • packages/ui-voip/src/views/ConfirmVideoEscalationModal/ConfirmVideoEscalationModal.stories.tsx
  • packages/ui-voip/src/views/ConfirmVideoEscalationModal/ConfirmVideoEscalationModal.tsx
  • packages/ui-voip/src/views/EscalatedCallPrompt.tsx
  • packages/ui-voip/src/views/MediaCallPopoutView.tsx
  • packages/ui-voip/src/views/MediaCallRoomSection/MediaCallRoomSection.tsx
  • packages/ui-voip/src/views/MediaCallWidget/OngoingCall.tsx
  • packages/ui-voip/src/views/MediaCallWidget/OngoingCallWithScreen.tsx
  • packages/ui-voip/src/views/PopupBlockedModal/PopupBlockedModal.spec.tsx
  • packages/ui-voip/src/views/PopupBlockedModal/PopupBlockedModal.stories.tsx
  • packages/ui-voip/src/views/PopupBlockedModal/PopupBlockedModal.tsx
  • packages/ui-voip/src/views/index.ts

Walkthrough

Adds Pexip-backed voice-to-video escalation across REST APIs, media services, SIP signaling, conference persistence, session state, and VoIP UI components. It also adds escalation settings, localization, call-feature contracts, SIP aliases, automatic escalation handling, and supporting stories/tests.

Changes

Escalation contracts and configuration

Layer / File(s) Summary
Settings, shared contracts, and REST endpoint
apps/meteor/..., ee/packages/media-calls/..., packages/core-typings/..., packages/rest-typings/...
Adds escalation settings, feature and hangup semantics, conference/call fields, service interfaces, and the authenticated POST /v1/media-calls.escalate contract.

Conference persistence and SIP aliases

Layer / File(s) Summary
Conference and media-call persistence
packages/models/..., packages/model-typings/...
Adds escalation queries and flags, conference-to-call associations, SIP alias indexes, alias setters, and cleanup when conferences become over.
Conference service integration
apps/meteor/server/services/video-conference/service.ts
Generates unique SIP aliases, assigns them to direct, group, and escalated conferences, exposes conference joining, and invokes provider join hooks.

Server escalation workflow

Layer / File(s) Summary
Media-call escalation orchestration
apps/meteor/server/services/media-call/service.ts, ee/packages/media-calls/src/server/*
Validates escalation requests, resolves or creates conference rooms, applies feature gating, flags calls, notifies participants, and handles escalated-call hangups.

SIP and Pexip integration

Layer / File(s) Summary
Pexip endpoint and URL behavior
packages/pexip/src/...
Resolves conferences by identifiers, detects SIP participants, conditionally omits guest PINs, injects escalation parameters, and auto-cleans escalated call legs after joins.
SIP escalation lifecycle
ee/packages/media-calls/src/sip/...
Adds Pexip URI and REFER handling, detects remote escalation, routes escalated calls through shared dialog teardown, and coordinates transfer/escalation termination paths.

Media signaling and VoIP UI

Layer / File(s) Summary
Signaling state and notifications
packages/media-signaling/..., ee/packages/media-calls/src/internal/...
Tracks escalated state and hangup reasons, emits escalation events, updates session transitions, and suppresses sound effects for selected termination states.
Escalation controls and prompts
packages/ui-voip/src/{components,hooks,providers,views}/...
Adds confirmation, popup-blocked, and escalated-call views; wires escalation requests and video opening into call context; and gates controls by supported features.
UI stories, tests, and localization
packages/ui-voip/**/*.stories.tsx, packages/ui-voip/**/*.spec.tsx, packages/i18n/src/locales/*
Adds component stories, snapshot/accessibility coverage, and English/German escalation strings.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: type: feature

Suggested reviewers: tassoevan


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 56.07477% with 94 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (dmv/base-for-video-escalation@91cff45). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@                       Coverage Diff                        @@
##             dmv/base-for-video-escalation   #40983   +/-   ##
================================================================
  Coverage                                 ?   69.15%           
================================================================
  Files                                    ?     3563           
  Lines                                    ?   140103           
  Branches                                 ?    24889           
================================================================
  Hits                                     ?    96890           
  Misses                                   ?    39201           
  Partials                                 ?     4012           
Flag Coverage Δ
unit 69.88% <56.07%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pierre-lehnen-rc
pierre-lehnen-rc force-pushed the dmv/voice-to-video-escalation branch 2 times, most recently from 5409bd9 to 741c235 Compare June 18, 2026 23:54
@pierre-lehnen-rc
pierre-lehnen-rc changed the base branch from develop to dmv/base-for-video-escalation June 19, 2026 00:12
ggazzo pushed a commit that referenced this pull request Jun 19, 2026
ggazzo pushed a commit that referenced this pull request Jun 19, 2026
@ggazzo
ggazzo force-pushed the dmv/voice-to-video-escalation branch from 2de9e16 to 9d3ef38 Compare June 19, 2026 17:09
@ggazzo
ggazzo force-pushed the dmv/base-for-video-escalation branch from d44d016 to 9c1451a Compare June 22, 2026 14:07
@ggazzo
ggazzo force-pushed the dmv/voice-to-video-escalation branch 2 times, most recently from 1700b61 to 14dbba1 Compare June 22, 2026 14:52
@ggazzo
ggazzo force-pushed the dmv/base-for-video-escalation branch from 9c1451a to 4b6cb58 Compare June 22, 2026 15:33
@ggazzo
ggazzo force-pushed the dmv/voice-to-video-escalation branch from 14dbba1 to 73f0764 Compare June 22, 2026 15:36
@ggazzo
ggazzo force-pushed the dmv/base-for-video-escalation branch from 4b6cb58 to cc99363 Compare June 22, 2026 15:52
@ggazzo
ggazzo force-pushed the dmv/voice-to-video-escalation branch from 73f0764 to b8ad2ae Compare June 22, 2026 15:52
@ggazzo
ggazzo force-pushed the dmv/base-for-video-escalation branch from cc99363 to 196d5bf Compare June 22, 2026 17:21
@ggazzo
ggazzo force-pushed the dmv/voice-to-video-escalation branch from e776d30 to bd59374 Compare June 22, 2026 17:26
ggazzo pushed a commit that referenced this pull request Jun 22, 2026
@pierre-lehnen-rc
pierre-lehnen-rc force-pushed the dmv/voice-to-video-escalation branch from 472884e to ea87e42 Compare June 22, 2026 18:55
ggazzo pushed a commit that referenced this pull request Jun 24, 2026
@pierre-lehnen-rc
pierre-lehnen-rc force-pushed the dmv/base-for-video-escalation branch from 196d5bf to bdf36d2 Compare June 24, 2026 21:04
@pierre-lehnen-rc
pierre-lehnen-rc force-pushed the dmv/voice-to-video-escalation branch from 382e1bd to 76a157b Compare June 24, 2026 21:06
@ggazzo
ggazzo force-pushed the dmv/base-for-video-escalation branch from bdf36d2 to 3ab8953 Compare June 24, 2026 21:11
@ggazzo
ggazzo force-pushed the dmv/voice-to-video-escalation branch from 76a157b to 4e0a746 Compare June 24, 2026 21:12
@ggazzo
ggazzo force-pushed the dmv/base-for-video-escalation branch from 3ab8953 to f62e6e0 Compare June 24, 2026 21:24
@pierre-lehnen-rc
pierre-lehnen-rc force-pushed the dmv/voice-to-video-escalation branch from 2bb686d to a761ff2 Compare July 14, 2026 22:50
@pierre-lehnen-rc
pierre-lehnen-rc changed the base branch from dmv/base-for-video-escalation to feat/voice-poc July 14, 2026 22:51
@pierre-lehnen-rc
pierre-lehnen-rc marked this pull request as ready for review July 14, 2026 22:58
@pierre-lehnen-rc
pierre-lehnen-rc requested review from a team as code owners July 14, 2026 22:58
@pierre-lehnen-rc
pierre-lehnen-rc merged commit dad8d2a into feat/voice-poc Jul 14, 2026
5 of 6 checks passed
@pierre-lehnen-rc
pierre-lehnen-rc deleted the dmv/voice-to-video-escalation branch July 14, 2026 22:59
@coderabbitai coderabbitai Bot added the type: feature Pull requests that introduces new feature label Jul 14, 2026

@hacktron-app hacktron-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Severity Count
HIGH 2

View full scan results

Comment on lines +67 to +120
private async detectVoiceCallEscalation(conference: VideoConference, participantUri: string | null): Promise<boolean> {
if (!participantUri) {
return false;
}

const { sipAlias, mediaCallIds: linkedMediaCallIds } = conference;

if (!sipAlias) {
return false;
}

const participantSipExtension = this.getIdentificationFromAlias(participantUri);

if (!participantSipExtension) {
logger.debug({ msg: 'Someone connected to a Pexip Conference via SIP, but we could not identify them.' });
return false;
}

try {
logger.debug({
msg: 'Pexip Participant joined via SIP',
sipAlias: conference.sipAlias,
conferenceId: conference._id,
participantSipExtension,
});

const mediaCallIds = await MediaCalls.findAllNotOverByOppositeSipExtension(participantSipExtension, { projection: { _id: 1 } })
.map(({ _id }) => _id)
.toArray();

if (mediaCallIds.length !== 1) {
// Check if the user is already linked to the conference
if (linkedMediaCallIds?.length) {
if (await MediaCalls.isUserSipExtensionInCallIds(participantSipExtension, linkedMediaCallIds)) {
return true;
}
}

logger.debug({ msg: 'Could not identify the media call that the SIP Participant is connecting from', calls: mediaCallIds });
return mediaCallIds.length > 0;
}

const [mediaCallId] = mediaCallIds;

const updateResult = await VideoConferenceModel.addMediaCallIdByConferenceId(conference._id, mediaCallId);
if (updateResult.modifiedCount) {
await MediaCall.flagAsRemotelyEscalatedByCallId(mediaCallId);
}
} catch (err) {
logger.error({ msg: 'Unexpected error handling Pexip Voice to Video Escalation', err });
}

return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH Pexip Conference Guest PIN Bypass via Unrelated Active Media Call Escalation

In packages/pexip/src/endpoints/serviceConfiguration.ts, the detectVoiceCallEscalation method is called during Pexip policy configuration requests (ServerConfigurationEndpoint.get) to determine if an incoming SIP participant is escalating an active voice call to a video conference. If this method returns true, the system sets canSkipPin to true, which completely removes the guest PIN requirement (guest_pin: null) for the requested conference.

However, the implementation of detectVoiceCallEscalation only checks if there is any active media call in the system for the connecting participant's SIP extension (MediaCalls.findAllNotOverByOppositeSipExtension(participantSipExtension)). It fails to verify whether that active media call has any association or relationship with the specific VideoConference being queried.

As a result, if any user with a SIP extension is currently in any active media call, an incoming SIP connection to any Pexip conference claiming to be from that SIP extension will trigger detectVoiceCallEscalation to return true. This bypasses the guest PIN requirement for that conference, allowing unauthorized users to join.

Steps to Reproduce
  1. Ensure the Rocket.Chat workspace has the Pexip integration enabled and configured.
  2. A legitimate user with a SIP extension (e.g., 1001) must currently be in an active, unfinished media call.
  3. An attacker connects to a private Pexip conference (e.g., conf-abc) via SIP, spoofing their caller ID / SIP URI to be sip:1001@....
  4. The Pexip server sends a policy request to Rocket.Chat's ServerConfigurationEndpoint.get with local_alias: "conf-abc" and remote_alias: "sip:1001@...".
  5. Rocket.Chat's detectVoiceCallEscalation finds the unrelated active call for extension 1001, associates it with conference conf-abc, and returns true.
  6. Rocket.Chat returns the service configuration to Pexip with guest_pin: null.
  7. The Pexip server allows the attacker (and any other guests) to join the private conference conf-abc without entering a PIN.
# An attacker can trigger this by sending a mock Pexip policy request to the Rocket.Chat Pexip endpoint, spoofing the SIP extension of an active caller:

curl -X GET "http://localhost:3000/api/v1/pexip.policy" \
  -H "Content-Type: application/json" \
  -d '{
    "local_alias": "unrelated-private-conference-id",
    "protocol": "sip",
    "remote_alias": "sip:ACTIVE_USER_SIP_EXTENSION@sip.domain.com"
  }'
Fix with AI

Open in Cursor Open in Claude

A security vulnerability was found by Hacktron.

File: packages/pexip/src/endpoints/serviceConfiguration.ts
Lines: 67-120
Severity: high

Vulnerability: Pexip Conference Guest PIN Bypass via Unrelated Active Media Call Escalation

Description:
In `packages/pexip/src/endpoints/serviceConfiguration.ts`, the `detectVoiceCallEscalation` method is called during Pexip policy configuration requests (`ServerConfigurationEndpoint.get`) to determine if an incoming SIP participant is escalating an active voice call to a video conference. If this method returns `true`, the system sets `canSkipPin` to `true`, which completely removes the guest PIN requirement (`guest_pin: null`) for the requested conference.

However, the implementation of `detectVoiceCallEscalation` only checks if there is any active media call in the system for the connecting participant's SIP extension (`MediaCalls.findAllNotOverByOppositeSipExtension(participantSipExtension)`). It fails to verify whether that active media call has any association or relationship with the specific `VideoConference` being queried.

As a result, if any user with a SIP extension is currently in any active media call, an incoming SIP connection to any Pexip conference claiming to be from that SIP extension will trigger `detectVoiceCallEscalation` to return `true`. This bypasses the guest PIN requirement for that conference, allowing unauthorized users to join.

Proof of Concept:
**Steps to Reproduce**

1. Ensure the Rocket.Chat workspace has the Pexip integration enabled and configured.
2. A legitimate user with a SIP extension (e.g., `1001`) must currently be in an active, unfinished media call.
3. An attacker connects to a private Pexip conference (e.g., `conf-abc`) via SIP, spoofing their caller ID / SIP URI to be `sip:1001@...`.
4. The Pexip server sends a policy request to Rocket.Chat's `ServerConfigurationEndpoint.get` with `local_alias: "conf-abc"` and `remote_alias: "sip:1001@..."`.
5. Rocket.Chat's `detectVoiceCallEscalation` finds the unrelated active call for extension `1001`, associates it with conference `conf-abc`, and returns `true`.
6. Rocket.Chat returns the service configuration to Pexip with `guest_pin: null`.
7. The Pexip server allows the attacker (and any other guests) to join the private conference `conf-abc` without entering a PIN.

```bash
# An attacker can trigger this by sending a mock Pexip policy request to the Rocket.Chat Pexip endpoint, spoofing the SIP extension of an active caller:

curl -X GET "http://localhost:3000/api/v1/pexip.policy" \
  -H "Content-Type: application/json" \
  -d '{
    "local_alias": "unrelated-private-conference-id",
    "protocol": "sip",
    "remote_alias": "sip:ACTIVE_USER_SIP_EXTENSION@sip.domain.com"
  }'
```

Affected Code:
	private async detectVoiceCallEscalation(conference: VideoConference, participantUri: string | null): Promise<boolean> {
		if (!participantUri) {
			return false;
		}

		const { sipAlias, mediaCallIds: linkedMediaCallIds } = conference;

		if (!sipAlias) {
			return false;
		}

		const participantSipExtension = this.getIdentificationFromAlias(participantUri);

		if (!participantSipExtension) {
			logger.debug({ msg: 'Someone connected to a Pexip Conference via SIP, but we could not identify them.' });
			return false;
		}

		try {
			logger.debug({
				msg: 'Pexip Participant joined via SIP',
				sipAlias: conference.sipAlias,
				conferenceId: conference._id,
				participantSipExtension,
			});

			const mediaCallIds = await MediaCalls.findAllNotOverByOppositeSipExtension(participantSipExtension, { projection: { _id: 1 } })
				.map(({ _id }) => _id)
				.toArray();

			if (mediaCallIds.length !== 1) {
				// Check if the user is already linked to the conference
				if (linkedMediaCallIds?.length) {
					if (await MediaCalls.isUserSipExtensionInCallIds(participantSipExtension, linkedMediaCallIds)) {
						return true;
					}
				}

				logger.debug({ msg: 'Could not identify the media call that the SIP Participant is connecting from', calls: mediaCallIds });
				return mediaCallIds.length > 0;
			}

			const [mediaCallId] = mediaCallIds;

			const updateResult = await VideoConferenceModel.addMediaCallIdByConferenceId(conference._id, mediaCallId);
			if (updateResult.modifiedCount) {
				await MediaCall.flagAsRemotelyEscalatedByCallId(mediaCallId);
			}
		} catch (err) {
			logger.error({ msg: 'Unexpected error handling Pexip Voice to Video Escalation', err });
		}

		return true;
	}

Acceptance criteria:
- Acceptance is defined by the **actual reported behavior**, not by tests passing.
- Reproduce the issue, or narrow the exact code path that produces it, *before* changing code. State what you confirmed.
- Fix the underlying cause. Mitigations that paper over the reported behavior do not count as a fix.
- Add a regression test that fails on the unpatched code and passes on the fix. If a regression test is genuinely impractical (e.g. race condition, infra-level issue), say so and explain why.
- Existing tests passing is **not** the bar. Do not declare done on tests-pass theatre.

Only change what is necessary to fix this vulnerability. Do not refactor adjacent code or modify unrelated files.

Triage: Reply !fp <reason> (false positive), !valid (confirmed), !accepted_risk <reason>, or !fixed (resolved). Any other reply is saved as a triage note.
Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing.

View finding in Hacktron

Comment on lines +484 to +522
public async escalateCall(uid: IUser['_id'], params: { callId: string }): Promise<string> {
const { callId } = params;

logger.debug({ msg: 'Escalating Voice Call', method: 'MediaCallService.escalateCall', uid, callId });

const call = await MediaCalls.findOneById(callId);

try {
if (!call?.acceptedAt || call.ended) {
throw new Error('not-found');
}

if (!call.uids.includes(uid)) {
throw new Error('not-found');
}
if (!call.features.includes('conference-escalation')) {
throw new Error('feature-not-available');
}

const user = await Users.findOneById(uid);
if (!user) {
throw new Error('internal-error');
}

const url = await this.escalateVoiceCallToConference(user, call);

// If the peer has also escalated this call, then we can hangup as we join the conference
if (call.escalatedByPeerAt) {
void callServer.hangupEscalatedCall(call, { type: 'user', id: user._id }).catch((err) => {
logger.error({ msg: 'Unexpected error while hanging up a fully escalated voice call', err });
});
}

return url;
} catch (err) {
logger.debug({ msg: 'Unexpected error during escalation', err, uid, callId, call });
throw err;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HIGH Missing Permission and Room Access Validation in Voice Call Escalation

The REST API endpoint media-calls.escalate and its corresponding service method MediaCallService.escalateCall allow any authenticated user to escalate an active voice call to a video conference. However, the implementation lacks critical authorization checks:

  1. No Video Conference Permission Check: It does not verify if the escalating user has the necessary permissions to start or join video conferences (e.g., call-management).
  2. No Room Access Validation: When escalating external calls, the target room defaults to the persistent chat external room (Pexip_Integration_PersistentChat_ExternalRoom) configured in settings. The method never validates whether the user uid actually has access to this target room before creating the conference and returning a join URL.

This allows an attacker who is a participant in a voice call to bypass video conference permissions and potentially gain unauthorized access/join links to video conferences in restricted rooms.

Steps to Reproduce
  1. Establish an active voice call with the conference-escalation feature enabled.
  2. Send a POST request to /api/v1/media-calls.escalate with the callId of the call as an authenticated user who does not have video conference permissions or access to the persistent chat external room.
  3. Observe that the API successfully returns a conference join URL.
Fix with AI

Open in Cursor Open in Claude

A security vulnerability was found by Hacktron.

File: apps/meteor/server/services/media-call/service.ts
Lines: 484-522
Severity: high

Vulnerability: Missing Permission and Room Access Validation in Voice Call Escalation

Description:
The REST API endpoint `media-calls.escalate` and its corresponding service method `MediaCallService.escalateCall` allow any authenticated user to escalate an active voice call to a video conference. However, the implementation lacks critical authorization checks:

1. **No Video Conference Permission Check**: It does not verify if the escalating user has the necessary permissions to start or join video conferences (e.g., `call-management`).
2. **No Room Access Validation**: When escalating external calls, the target room defaults to the persistent chat external room (`Pexip_Integration_PersistentChat_ExternalRoom`) configured in settings. The method never validates whether the user `uid` actually has access to this target room before creating the conference and returning a join URL.

This allows an attacker who is a participant in a voice call to bypass video conference permissions and potentially gain unauthorized access/join links to video conferences in restricted rooms.

Proof of Concept:
**Steps to Reproduce**

1. Establish an active voice call with the `conference-escalation` feature enabled.
2. Send a POST request to `/api/v1/media-calls.escalate` with the `callId` of the call as an authenticated user who does not have video conference permissions or access to the persistent chat external room.
3. Observe that the API successfully returns a conference join URL.

Affected Code:
	public async escalateCall(uid: IUser['_id'], params: { callId: string }): Promise<string> {
		const { callId } = params;

		logger.debug({ msg: 'Escalating Voice Call', method: 'MediaCallService.escalateCall', uid, callId });

		const call = await MediaCalls.findOneById(callId);

		try {
			if (!call?.acceptedAt || call.ended) {
				throw new Error('not-found');
			}

			if (!call.uids.includes(uid)) {
				throw new Error('not-found');
			}
			if (!call.features.includes('conference-escalation')) {
				throw new Error('feature-not-available');
			}

			const user = await Users.findOneById(uid);
			if (!user) {
				throw new Error('internal-error');
			}

			const url = await this.escalateVoiceCallToConference(user, call);

			// If the peer has also escalated this call, then we can hangup as we join the conference
			if (call.escalatedByPeerAt) {
				void callServer.hangupEscalatedCall(call, { type: 'user', id: user._id }).catch((err) => {
					logger.error({ msg: 'Unexpected error while hanging up a fully escalated voice call', err });
				});
			}

			return url;
		} catch (err) {
			logger.debug({ msg: 'Unexpected error during escalation', err, uid, callId, call });
			throw err;
		}
	}

Acceptance criteria:
- Acceptance is defined by the **actual reported behavior**, not by tests passing.
- Reproduce the issue, or narrow the exact code path that produces it, *before* changing code. State what you confirmed.
- Fix the underlying cause. Mitigations that paper over the reported behavior do not count as a fix.
- Add a regression test that fails on the unpatched code and passes on the fix. If a regression test is genuinely impractical (e.g. race condition, infra-level issue), say so and explain why.
- Existing tests passing is **not** the bar. Do not declare done on tests-pass theatre.

Only change what is necessary to fix this vulnerability. Do not refactor adjacent code or modify unrelated files.

Triage: Reply !fp <reason> (false positive), !valid (confirmed), !accepted_risk <reason>, or !fixed (resolved). Any other reply is saved as a triage note.
Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing.

View finding in Hacktron

pierre-lehnen-rc added a commit that referenced this pull request Jul 15, 2026
Co-authored-by: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat>
Co-authored-by: gabriellsh <henriques.gabriell@gmail.com>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
pierre-lehnen-rc added a commit that referenced this pull request Jul 21, 2026
Co-authored-by: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat>
Co-authored-by: gabriellsh <henriques.gabriell@gmail.com>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
pierre-lehnen-rc added a commit that referenced this pull request Jul 22, 2026
Co-authored-by: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat>
Co-authored-by: gabriellsh <henriques.gabriell@gmail.com>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
pierre-lehnen-rc added a commit that referenced this pull request Jul 22, 2026
Co-authored-by: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat>
Co-authored-by: gabriellsh <henriques.gabriell@gmail.com>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
pierre-lehnen-rc added a commit that referenced this pull request Jul 22, 2026
Co-authored-by: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat>
Co-authored-by: gabriellsh <henriques.gabriell@gmail.com>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

add-to-poc type: feature Pull requests that introduces new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants