Skip to content

fix(e2ee): stop sending msg when there's content#6818

Merged
diegolmello merged 1 commit intodevelopfrom
fix.e2ee-msg
Nov 27, 2025
Merged

fix(e2ee): stop sending msg when there's content#6818
diegolmello merged 1 commit intodevelopfrom
fix.e2ee-msg

Conversation

@diegolmello
Copy link
Member

@diegolmello diegolmello commented Nov 27, 2025

Proposed changes

Removed msg when there's content.
Although I was not able to reproduce it, that was the reason it was breaking web app.

Tested both on v1 (7.12.0) and v2 (7.13.0).

Issue(s)

Closes #6817
https://rocketchat.atlassian.net/browse/CORE-1525

How to test or reproduce

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • Refactor
    • Consolidated encryption flow into a single path for all message types, removing the upload-specific branch.
    • File message sending now includes structured content when present and omits raw message text in that case.
    • Improved error handling for encryption with a safe fallback to the original message on failure.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 27, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Consolidates upload encryption into encrypt() by removing encryptUpload() and its IUpload import; encrypt() now returns a local encryptedMessage (drops msg, includes content) and logs/returns the original message on error. Adjusts rooms.mediaConfirm to omit msg when content exists and include content in the payload.

Changes

Cohort / File(s) Summary
Encryption method consolidation
app/lib/encryption/room.ts
Removed encryptUpload(message: IUpload) and the IUpload public import. Refactored encrypt() to construct and return a local encryptedMessage containing t, e2e, e2eMentions, and content, deleting the msg field; on error it logs and returns the original message.
File message payload restructuring
app/lib/methods/sendFileMessage/sendFileMessageV2.ts
Modified rooms.mediaConfirm request body: when content exists, msg is resolved to undefined (omitted) and content is added to the payload alongside tmid, description, and optional t.

Sequence Diagram(s)

(omitted — changes are refactor/payload tweaks without new multi-component control flow)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Pay special attention to:
    • Any remaining call sites expecting encryptUpload().
    • Upload handling and type expectations after removing IUpload from exports.
    • Consumers and server endpoints that expect msg in rooms.mediaConfirm payloads when content is present.
    • Error-path behavior in encrypt() to ensure it doesn't produce invalid messages for encrypted channels.

Possibly related PRs

Suggested reviewers

  • OtavioStasiak

Poem

🐇 I hopped through code with nimble feet,
One encrypt now keeps uploads neat.
Messages trimmed, payloads tidy —
I munched a bug and left it tidy.
Thump-thump, commit complete! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: preventing the msg field from being sent when content is present in encrypted file messages.
Linked Issues check ✅ Passed The changes address the core issue by refactoring encryption handling to prevent malformed messages from being sent when content is present, which aligns with the bug report about invalid messages breaking encrypted channels.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the msg/content issue in encrypted file messages; no unrelated modifications detected beyond the stated objectives.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between feb29c7 and 51fb3b1.

📒 Files selected for processing (2)
  • app/lib/encryption/room.ts (1 hunks)
  • app/lib/methods/sendFileMessage/sendFileMessageV2.ts (1 hunks)

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/lib/encryption/room.ts (1)

18-27: Remove unused IUpload import to fix lint failure

IUpload is still imported from ../../definitions but no longer used (per the pipeline no-unused-vars error). This will keep CI red until removed.

Suggested diff:

-import type {
-	IAttachment,
-	IMessage,
-	IUpload,
-	TSendFileMessageFileInfo,
-	IServerAttachment,
-	TSubscriptionModel,
-	ISubscription
-} from '../../definitions';
+import type {
+	IAttachment,
+	IMessage,
+	TSendFileMessageFileInfo,
+	IServerAttachment,
+	TSubscriptionModel,
+	ISubscription
+} from '../../definitions';
🧹 Nitpick comments (1)
app/lib/methods/sendFileMessage/sendFileMessageV2.ts (1)

65-81: Clarify msg omission logic when content is present

(content ? '' : file.msg) || undefined does achieve “no msg when there’s content” (since '' || undefined evaluates to undefined), and only sends msg when content is falsy and file.msg is non‑empty. The behaviour looks correct for the PR goal, but the intent is a bit opaque and also drops msg when file.msg === ''.

You could make this clearer and equivalent with:

msg: content ? undefined : file.msg || undefined,

which reads more directly as “if we have encrypted content, don’t send msg; otherwise send a non‑empty caption if present.”

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3e090fb and 76a9d4b.

📒 Files selected for processing (2)
  • app/lib/encryption/room.ts (1 hunks)
  • app/lib/methods/sendFileMessage/sendFileMessageV2.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/lib/encryption/room.ts (3)
app/lib/constants/keys.ts (2)
  • E2E_MESSAGE_TYPE (1-1)
  • E2E_STATUS (5-8)
app/lib/encryption/utils.ts (1)
  • getE2EEMentions (92-103)
app/definitions/IMessage.ts (1)
  • IMessage (147-178)
🪛 GitHub Actions: Format Code with Prettier
app/lib/encryption/room.ts

[error] 22-22: 'IUpload' is defined but never used. (no-unused-vars)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🔇 Additional comments (1)
app/lib/encryption/room.ts (1)

476-502: Encrypted message shape now consistently uses content and drops msg

The new encrypt() implementation looks sound:

  • It wraps the plaintext in content via encryptText(EJSON.stringify({ msg: message.msg || '' })).
  • It marks the message as E2E (t: E2E_MESSAGE_TYPE, e2e: E2E_STATUS.PENDING) and computes e2eMentions from the original msg.
  • It deletes encryptedMessage.msg, so we no longer send plaintext msg alongside encrypted content.

This remains compatible with decrypt(), which reads message.content || message.msg and spreads the decrypted payload back, so new and legacy messages should both decrypt correctly.

I’d keep this as‑is; just ensure server‑side E2E handling is aligned with the “content only, no msg” contract for all relevant versions.

@diegolmello diegolmello temporarily deployed to experimental_android_build November 27, 2025 14:49 — with GitHub Actions Inactive
@diegolmello diegolmello temporarily deployed to experimental_ios_build November 27, 2025 14:49 — with GitHub Actions Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/lib/methods/sendFileMessage/sendFileMessageV2.ts (1)

76-76: Consider simplifying the conditional expression.

The logic correctly omits msg when content is present, but the expression (content ? '' : file.msg) || undefined relies on empty string's falsiness, making it less immediately clear.

Apply this diff for improved readability:

-				msg: (content ? '' : file.msg) || undefined,
+				msg: content ? undefined : file.msg,

Both expressions produce the same result, but the simplified version more directly expresses the intent: when content exists, omit msg; otherwise, include file.msg (which may itself be undefined).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 76a9d4b and feb29c7.

📒 Files selected for processing (2)
  • app/lib/encryption/room.ts (1 hunks)
  • app/lib/methods/sendFileMessage/sendFileMessageV2.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/lib/encryption/room.ts (3)
app/lib/constants/keys.ts (2)
  • E2E_MESSAGE_TYPE (1-1)
  • E2E_STATUS (5-8)
app/lib/encryption/utils.ts (1)
  • getE2EEMentions (92-103)
app/definitions/IMessage.ts (1)
  • IMessage (147-178)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ESLint and Test / run-eslint-and-test
🔇 Additional comments (1)
app/lib/encryption/room.ts (1)

484-494: LGTM: Encryption logic correctly constructs content-only encrypted messages.

The refactored encryption flow properly:

  • Encrypts the message text into a structured content object (line 482)
  • Constructs an encrypted message with e2e metadata and the encrypted content (lines 484-490)
  • Explicitly deletes the msg field to prevent sending plaintext alongside encrypted content (line 492)

This directly addresses the PR objective of preventing msg from being sent when encrypted content is present.

Comment on lines +495 to 500
} catch (e) {
// Do nothing
console.error(e);
}

return message;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all calls to .encrypt() and examine their context

echo "=== Finding .encrypt() method calls ==="
rg -n -C4 '\.encrypt\s*\(' --type=ts | head -100

echo ""
echo "=== Finding Encryption class references ==="
rg -n -C3 'Encryption\.(encrypt|encryptMessage)' --type=ts | head -50

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 2738


🏁 Script executed:

#!/bin/bash
# Examine Encryption.encryptMessage() implementation

echo "=== Examining Encryption class encryptMessage method ==="
rg -n -A20 'encryptMessage\s*\(' app/lib/encryption/encryption.ts | head -80

echo ""
echo "=== Examining sendMessage.ts context around Encryption.encryptMessage calls ==="
sed -n '60,120p' app/lib/methods/sendMessage.ts

echo ""
echo "=== Checking if there's message validation after encryption ==="
rg -n -C5 'content' app/lib/methods/sendMessage.ts | head -60

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 1717


🏁 Script executed:

#!/bin/bash
# Get full context of Encryption class

echo "=== Finding Encryption.encryptMessage full implementation ==="
rg -n 'encryptMessage' app/lib/encryption/encryption.ts -A30 | head -100

echo ""
echo "=== Finding sendMessageCall implementation ==="
rg -n -B3 -A15 'sendMessageCall\s*\(' app/lib/methods/sendMessage.ts | head -80

echo ""
echo "=== Full context of EncryptionRoom.encrypt() in room.ts ==="
sed -n '470,510p' app/lib/encryption/room.ts

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 3288


🏁 Script executed:

#!/bin/bash
# Check if there's any server-side validation or other client guards

echo "=== Search for any validation of encrypted content before sending ==="
rg -n 'chat\.sendMessage' app/lib --type=ts -B5 -A5

echo ""
echo "=== Check for any guards on message.content or message.msg ==="
rg -n -C3 'message\.content\|message\.msg' app/lib/methods/sendMessage.ts

echo ""
echo "=== Check what happens with undefined return from encryptMessage ==="
rg -n -C5 'Encryption\.encryptMessage' app/lib/methods/sendMessage.ts

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 1629


🏁 Script executed:

#!/bin/bash
# Final check: Look at what properties the encrypted message should have

echo "=== Check IMessage type to understand expected structure ==="
fd -type f -name "*.ts" | xargs grep -l "interface IMessage\|type IMessage" | head -5

echo ""
echo "=== Check E2E_MESSAGE_TYPE to understand encrypted messages ==="
rg -n "E2E_MESSAGE_TYPE\|E2E_STATUS" app/lib/encryption --type=ts -B2 -A2 | head -40

Repository: RocketChat/Rocket.Chat.ReactNative

Length of output: 391


Based on my verification, I have found critical security issues with the error handling in the encryption code. Let me provide the rewritten review comment:


Encryption failure silently sends plaintext messages without validation.

When encryption fails (lines 495-500), EncryptionRoom.encrypt() returns the original unencrypted message. This fallback is problematic in encrypted rooms because:

  1. Encryption.encryptMessage() can return unencrypted messages or undefined:

    • Line 542: Returns undefined if room has no session key
    • Line 551: Returns unencrypted message if catch block executes
    • Line 537: Returns unencrypted message for non-encrypted subscriptions (intentional but indistinguishable from failures)
  2. Calling code has no validation:

    • sendMessage() and resendMessage() (app/lib/methods/sendMessage.ts) don't validate the return value
    • They pass whatever is returned directly to sendMessageCall() which sends it to the API
    • No check for undefined, no check for plaintext msg field vs encrypted content field
  3. Inconsistent with editMessage():

    • editMessage() (app/lib/services/restApi.ts:967-969) explicitly checks: if (!result) throw new Error('Failed to encrypt message')
    • Message send paths lack this guard, creating different security guarantees

Required action: Add explicit null/undefined check in sendMessage() and resendMessage() before calling sendMessageCall(), and/or differentiate between intentional fallback (non-encrypted room) vs error cases. Users expect E2E encryption; silent plaintext fallback is a security risk.

🤖 Prompt for AI Agents
In app/lib/encryption/room.ts around lines 495-500, EncryptionRoom.encrypt()
currently catches errors and returns the original plaintext message, which can
cause silent plaintext sends; change behavior so encryption failures do not
return the original plaintext (either return undefined/null or throw) and ensure
the catch logs error details but does not fall back to plaintext. Then update
callers app/lib/methods/sendMessage.ts (sendMessage and resendMessage) to
explicitly check the encrypt() result before calling sendMessageCall(): if
encrypt() returns undefined/null or an error is thrown, abort sending and
surface/throw a clear "Failed to encrypt message" error (or return a failure
result) rather than sending plaintext; alternatively, make encrypt() return a
distinct sentinel for non-encrypted rooms and have callers treat that
differently from an encryption failure.

@diegolmello diegolmello had a problem deploying to upload_experimental_android November 27, 2025 15:19 — with GitHub Actions Error
@github-actions
Copy link

Android Build Available

Rocket.Chat Experimental 4.67.0.107750

Internal App Sharing: https://play.google.com/apps/test/RQVpXLytHNc/ahAO29uNQt__e0fa4oT7lN2awbuhsKv2Sa5uCqjfSEzh8VwafFq9pU1pLMttQh1pkjP3DkDqA8076-JHVim5d4BsWM

@github-actions
Copy link

iOS Build Available

Rocket.Chat Experimental 4.67.0.107751

Copy link
Contributor

@OtavioStasiak OtavioStasiak left a comment

Choose a reason for hiding this comment

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

Looks good to me!

@diegolmello diegolmello merged commit bcd351f into develop Nov 27, 2025
3 of 4 checks passed
@diegolmello diegolmello deleted the fix.e2ee-msg branch November 27, 2025 17:57
@diegolmello diegolmello had a problem deploying to official_android_build November 27, 2025 18:01 — with GitHub Actions Failure
@diegolmello diegolmello had a problem deploying to experimental_ios_build November 27, 2025 18:01 — with GitHub Actions Failure
@diegolmello diegolmello had a problem deploying to experimental_android_build November 27, 2025 18:01 — with GitHub Actions Failure
@coderabbitai coderabbitai bot mentioned this pull request Dec 2, 2025
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid client messages are not validated - break encrypted channels and block web clients

2 participants