feat: Pexip Integration - #40847
Conversation
|
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #40847 +/- ##
===========================================
- Coverage 69.29% 69.28% -0.02%
===========================================
Files 3539 3542 +3
Lines 138832 138920 +88
Branches 24767 24760 -7
===========================================
+ Hits 96210 96245 +35
- Misses 38607 38640 +33
- Partials 4015 4035 +20
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (30)
WalkthroughAdds a new ChangesPexip contracts and configuration
Pexip client and provider behavior
API and video-conference integration
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Pexip as Pexip
participant API as Rocket.Chat API
participant Client as Pexip client
participant Provider as PexipVideoConfProvider
participant Model as VideoConferenceModel
Pexip->>API: POST events or GET service configuration
API->>Client: validateRequestCredentials
API->>Client: processEvent or getServiceConfiguration
Client->>Model: update status or load conference
Client-->>API: success or service configuration
Provider->>Client: generate and store conference PINs
Client->>Model: setProviderDataById
Provider-->>API: conference URL and provider information
Suggested labels: Suggested reviewers: 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. Comment |
| public validateRequestCredentials(authHeader?: string | null): void { | ||
| const { api } = this.settings; | ||
|
|
||
| if (!api.username) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
Authentication Bypass and Fail-Open in Pexip Integration Endpoints leading to PIN Leakage and Call Termination
The Pexip integration endpoints (/api/pexip/policy/v1/service/configuration and /api/pexip/events) suffer from an authentication bypass and a fail-open design.
- Fail-Open Authentication: In
Pexip.validateRequestCredentials, if theapi.usernamesetting is not configured (which is the default state, wherePexip_Integration_API_Usernameis""), the function returns immediately without throwing an error. This allows any request to bypass authentication checks. - Missing Integration Enabled Check: Neither
policyServer.tsnoreventSink.tsverifies whether the Pexip integration is actually enabled (Pexip_Integration_Enabledsetting). Consequently, these endpoints are exposed and functional by default on all deployments.
Exploitation Path & Impact:
- Information Disclosure: An unauthenticated remote attacker can query
/api/pexip/policy/v1/service/configuration?local_alias=<callId>to retrieve the host and guest PINs for any active or past video conference call in the system. - Unauthorized Call Termination (DoS): An unauthenticated remote attacker can send a POST request to
/api/pexip/eventswith aconference_endedevent payload to prematurely set the status of any active video conference call toENDED, disrupting ongoing meetings.
Steps to Reproduce
- Ensure the Pexip integration is in its default state (disabled, and API username is empty).
- Create a video conference call in Rocket.Chat and obtain its ID (TARGET_CALL_ID).
- Send a GET request to
/api/pexip/policy/v1/service/configuration?local_alias=TARGET_CALL_IDwithout anyAuthorizationheader. - Observe that the endpoint returns the host and guest PINs for the conference.
- Send a POST request to
/api/pexip/eventswith aconference_endedevent payload matching the TARGET_CALL_ID. - Observe that the conference status is prematurely mutated to
ENDED.
# Retrieve PINs for any call ID
curl -X GET "http://localhost:3000/api/pexip/policy/v1/service/configuration?local_alias=TARGET_CALL_ID"
# Terminate any active call ID
curl -X POST "http://localhost:3000/api/pexip/events" \
-H "Content-Type: application/json" \
-d '{"event": "conference_ended", "data": {"name": "TARGET_CALL_ID"}}'Fix with AI
A security vulnerability was found by Hacktron.
File: packages/pexip/src/Pexip.ts
Lines: 17-22
Severity: high
Vulnerability: Authentication Bypass and Fail-Open in Pexip Integration Endpoints leading to PIN Leakage and Call Termination
Description:
The Pexip integration endpoints (`/api/pexip/policy/v1/service/configuration` and `/api/pexip/events`) suffer from an authentication bypass and a fail-open design.
1. **Fail-Open Authentication**: In `Pexip.validateRequestCredentials`, if the `api.username` setting is not configured (which is the default state, where `Pexip_Integration_API_Username` is `""`), the function returns immediately without throwing an error. This allows any request to bypass authentication checks.
2. **Missing Integration Enabled Check**: Neither `policyServer.ts` nor `eventSink.ts` verifies whether the Pexip integration is actually enabled (`Pexip_Integration_Enabled` setting). Consequently, these endpoints are exposed and functional by default on all deployments.
### Exploitation Path & Impact:
- **Information Disclosure**: An unauthenticated remote attacker can query `/api/pexip/policy/v1/service/configuration?local_alias=<callId>` to retrieve the host and guest PINs for any active or past video conference call in the system.
- **Unauthorized Call Termination (DoS)**: An unauthenticated remote attacker can send a POST request to `/api/pexip/events` with a `conference_ended` event payload to prematurely set the status of any active video conference call to `ENDED`, disrupting ongoing meetings.
Proof of Concept:
**Steps to Reproduce**
1. Ensure the Pexip integration is in its default state (disabled, and API username is empty).
2. Create a video conference call in Rocket.Chat and obtain its ID (TARGET_CALL_ID).
3. Send a GET request to `/api/pexip/policy/v1/service/configuration?local_alias=TARGET_CALL_ID` without any `Authorization` header.
4. Observe that the endpoint returns the host and guest PINs for the conference.
5. Send a POST request to `/api/pexip/events` with a `conference_ended` event payload matching the TARGET_CALL_ID.
6. Observe that the conference status is prematurely mutated to `ENDED`.
```bash
# Retrieve PINs for any call ID
curl -X GET "http://localhost:3000/api/pexip/policy/v1/service/configuration?local_alias=TARGET_CALL_ID"
# Terminate any active call ID
curl -X POST "http://localhost:3000/api/pexip/events" \
-H "Content-Type: application/json" \
-d '{"event": "conference_ended", "data": {"name": "TARGET_CALL_ID"}}'
```
Affected Code:
public validateRequestCredentials(authHeader?: string | null): void {
const { api } = this.settings;
if (!api.username) {
return;
}
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.
| public async createHostPin(call: VideoConference | undefined, room: IRoom | undefined, user: IUser | undefined): Promise<string> { | ||
| const { pins } = this.settings; | ||
|
|
||
| if (pins.host) { | ||
| return pins.host; | ||
| } | ||
|
|
||
| // TODO: secure pins | ||
|
|
||
| if (call) { | ||
| return this.getPinFromString(`${call._id}${call.createdBy._id}`); | ||
| } | ||
|
|
||
| if (room) { | ||
| return this.getPinFromString(`${room._id}_hosts`); | ||
| } | ||
|
|
||
| if (user) { | ||
| return this.getPinFromString(`${user._id}_host`); | ||
| } | ||
|
|
||
| return ''; | ||
| } |
There was a problem hiding this comment.
Predictable Host and Guest PIN Generation in Pexip Integration
The Pexip video conference integration generates Host and Guest PINs deterministically when static PINs are not configured in the settings (which is the default configuration). In packages/pexip/src/Pexip.ts, the createHostPin function generates the host PIN using this.getPinFromString('${call._id}${call.createdBy._id}') and createGuestPin generates the guest PIN using this.getPinFromString('${call._id}${call.rid}'). The getPinFromString function hashes this identifier using SHA-256 and then extracts the last 6 digits of its decimal representation via getPinFromHash. Because call._id (the call ID), call.createdBy._id (the creator's user ID), and call.rid (the room ID) are non-secret, structured identifiers that are visible to all participants in the chat room (and transmitted via room messages or the REST/DDP APIs), any standard user or guest in the room can easily compute the Host PIN. This allows any participant to authenticate as the Host/Moderator in the Pexip meeting, bypassing the intended authorization check where only the legitimate host should see and use the Host PIN.
Steps to Reproduce
- Enable the Pexip integration in Rocket.Chat settings without configuring static Host/Guest PINs.
- As a regular user, join a room where a Pexip video conference is started by a host.
- Retrieve the
callId(from the message block), the room ID (rid), and the creator's user ID (createdBy._id). - Run the Python script or equivalent code to calculate the Host PIN using the formula:
SHA-256(callId + creatorId)converted to BigInt, taking the last 6 digits. - Join the Pexip meeting and enter the calculated Host PIN to gain Host/Moderator privileges.
import hashlib
def get_pin_from_hash(hex_hash):
return str(int(hex_hash, 16))[-6:]
def get_pin_from_string(identifier):
hex_hash = hashlib.sha256(identifier.encode('utf-8')).hexdigest()
return get_pin_from_hash(hex_hash)
# Example inputs obtained from room metadata
call_id = "exampleCallId123"
creator_id = "exampleCreatorId456"
# Calculate the Host PIN
host_pin = get_pin_from_string(f"{call_id}{creator_id}")
print(f"Calculated Host PIN: {host_pin}")Fix with AI
A security vulnerability was found by Hacktron.
File: packages/pexip/src/Pexip.ts
Lines: 91-113
Severity: high
Vulnerability: Predictable Host and Guest PIN Generation in Pexip Integration
Description:
The Pexip video conference integration generates Host and Guest PINs deterministically when static PINs are not configured in the settings (which is the default configuration). In `packages/pexip/src/Pexip.ts`, the `createHostPin` function generates the host PIN using `this.getPinFromString('${call._id}${call.createdBy._id}')` and `createGuestPin` generates the guest PIN using `this.getPinFromString('${call._id}${call.rid}')`. The `getPinFromString` function hashes this identifier using SHA-256 and then extracts the last 6 digits of its decimal representation via `getPinFromHash`. Because `call._id` (the call ID), `call.createdBy._id` (the creator's user ID), and `call.rid` (the room ID) are non-secret, structured identifiers that are visible to all participants in the chat room (and transmitted via room messages or the REST/DDP APIs), any standard user or guest in the room can easily compute the Host PIN. This allows any participant to authenticate as the Host/Moderator in the Pexip meeting, bypassing the intended authorization check where only the legitimate host should see and use the Host PIN.
Proof of Concept:
**Steps to Reproduce**
1. Enable the Pexip integration in Rocket.Chat settings without configuring static Host/Guest PINs.
2. As a regular user, join a room where a Pexip video conference is started by a host.
3. Retrieve the `callId` (from the message block), the room ID (`rid`), and the creator's user ID (`createdBy._id`).
4. Run the Python script or equivalent code to calculate the Host PIN using the formula: `SHA-256(callId + creatorId)` converted to BigInt, taking the last 6 digits.
5. Join the Pexip meeting and enter the calculated Host PIN to gain Host/Moderator privileges.
```python
import hashlib
def get_pin_from_hash(hex_hash):
return str(int(hex_hash, 16))[-6:]
def get_pin_from_string(identifier):
hex_hash = hashlib.sha256(identifier.encode('utf-8')).hexdigest()
return get_pin_from_hash(hex_hash)
# Example inputs obtained from room metadata
call_id = "exampleCallId123"
creator_id = "exampleCreatorId456"
# Calculate the Host PIN
host_pin = get_pin_from_string(f"{call_id}{creator_id}")
print(f"Calculated Host PIN: {host_pin}")
```
Affected Code:
public async createHostPin(call: VideoConference | undefined, room: IRoom | undefined, user: IUser | undefined): Promise<string> {
const { pins } = this.settings;
if (pins.host) {
return pins.host;
}
// TODO: secure pins
if (call) {
return this.getPinFromString(`${call._id}${call.createdBy._id}`);
}
if (room) {
return this.getPinFromString(`${room._id}_hosts`);
}
if (user) {
return this.getPinFromString(`${user._id}_host`);
}
return '';
}
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.
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
Proposed changes (including videos or screenshots)
wip, very rushed version of a pexip integration within rocket.chat core.
Issue(s)
DMV-48
Steps to test or reproduce
Further comments
The path for the pexip API in this integration is
/api/pexipFor example:
Policy Server URL:
https://rocket.chat/api/pexipEvent Sink URL:
https://rocket.chat/api/pexip/eventsIf you had any pexip env configured to use the Rocket.Chat app, you need to replace
apps/public/7fc612f5-b585-4e46-8c4e-6d2c13e2f451bypexipin its configurationSummary by CodeRabbit