Skip to content

fix(scheduled): enforce XSS sanitization & Cloudinary validation for scheduled messages#584

Closed
harrshita123 wants to merge 4 commits into
UTKARSHH20:mainfrom
harrshita123:fix/scheduled-message-security
Closed

fix(scheduled): enforce XSS sanitization & Cloudinary validation for scheduled messages#584
harrshita123 wants to merge 4 commits into
UTKARSHH20:mainfrom
harrshita123:fix/scheduled-message-security

Conversation

@harrshita123

@harrshita123 harrshita123 commented Jun 13, 2026

Copy link
Copy Markdown

This PR resolves #563.

Scheduled messages were previously stored without XSS sanitization and without Cloudinary validation for image/audio attachments. The changes add XSS sanitisation, enforce attachment MIME and size validation, upload attachments to Cloudinary, and refactor validation logic into a shared module. This brings the scheduled-message flow in line with the live-message security standards.

Summary by CodeRabbit

  • New Features

    • Added support for image and audio attachments in scheduled messages.
  • Bug Fixes

    • Enhanced input validation for scheduled messages to enforce required fields and future-dated scheduling.
    • Added automatic attachment size limits (5MB for images, 10MB for audio).
    • Improved message content security through sanitization.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@harrshita123, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 52 minutes and 54 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 28473f8d-89ba-4b5a-bd05-2907ac92cabb

📥 Commits

Reviewing files that changed from the base of the PR and between 18c8049 and 6d48ae6.

📒 Files selected for processing (4)
  • backend/src/controllers/scheduledMessage.controller.js
  • backend/src/lib/attachmentValidator.js
  • backend/src/lib/messageScheduler.js
  • backend/src/models/scheduledMessage.model.js
📝 Walkthrough

Walkthrough

This PR closes a stored-XSS vulnerability and attachment-validation gap in scheduled messages. A new attachmentValidator.js module validates Base64 image and audio payloads. The scheduleMessage controller now sanitizes message text and validates/uploads attachments to Cloudinary at schedule time. The processScheduledMessage scheduler repeats sanitization and attachment handling at delivery time, ensuring scheduled messages receive the same security and media-handling treatment as live messages.

Changes

Scheduled Message Sanitization and Attachment Validation

Layer / File(s) Summary
Base64 attachment validators for image and audio
backend/src/lib/attachmentValidator.js
New module exports validateImageAttachment and validateAudioAttachment, each validating Base64 data-URL structure, enforcing MIME allow-lists (common image and audio formats), estimating decoded payload size from Base64 length and padding, enforcing size limits (5MB for images, 10MB for audio by default), and returning { isValid: true } or { isValid: false, error: <message> }.
Controller-side request validation and Cloudinary upload
backend/src/controllers/scheduledMessage.controller.js
scheduleMessage adds structured validation: receiver ID as Mongo ObjectId, scheduledFor required and enforced in the future, at least one of message/image/audio required, receiver user existence verified. Message text is sanitized via xss. Image and audio attachments are validated with the new validators, uploaded to Cloudinary, and only the resulting secure_url values are stored in the created ScheduledMessage.
Scheduler-side delivery with sanitization and attachment resolution
backend/src/lib/messageScheduler.js
processScheduledMessage sanitizes scheduledMsg.message with xss, validates data:-encoded image/audio payloads, uploads validated data: payloads to Cloudinary (with resource_type: "auto" for audio), treats non-data: strings as existing URLs, and constructs the outgoing Message using sanitized/resolved values instead of raw scheduled fields. Invalid or oversized attachments cause the scheduled message to be skipped.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A message scheduler once stored XSS,
With base64 blobs in unholy bliss,
Now sanitized text and Cloudinary URLs bright,
Make scheduled sends secure and right! ✨📧

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main changes: XSS sanitization and Cloudinary validation enforcement for scheduled messages, matching the primary objectives.
Linked Issues check ✅ Passed All objectives from issue #563 are met: XSS sanitization via xss() [controller/lib], attachment validation with MIME/size limits [attachmentValidator], Cloudinary upload for valid attachments [controller/lib], and shared validation refactoring [attachmentValidator module].
Out of Scope Changes check ✅ Passed All changes are directly scoped to the issue: scheduled message sanitization, attachment validation, Cloudinary uploads, and validator module refactoring. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
backend/src/controllers/scheduledMessage.controller.js (1)

190-194: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Security gap: updateScheduledMessage bypasses XSS sanitization and attachment validation.

The PR adds sanitization and validation to scheduleMessage, but updateScheduledMessage directly assigns message, image, and audio without:

  1. Sanitizing message with xss()
  2. Validating image/audio attachments
  3. Uploading validated attachments to Cloudinary

An attacker can schedule a benign message, then PATCH it to inject malicious XSS or oversized/invalid base64. While the scheduler re-sanitizes at delivery time, the stored ScheduledMessage and API responses (getScheduledMessage, getScheduledMessages) will return unsanitized content that clients may render in previews.

🔒 Proposed fix
         // Update fields if provided
-        if (message !== undefined) scheduledMessage.message = message;
-        if (image !== undefined) scheduledMessage.image = image;
-        if (audio !== undefined) scheduledMessage.audio = audio;
+        if (message !== undefined) scheduledMessage.message = xss(message.trim());
+        
+        if (image !== undefined) {
+            if (image) {
+                const validation = validateImageAttachment(image);
+                if (!validation.isValid) {
+                    return res.status(400).json({ message: validation.error });
+                }
+                const result = await cloudinary.uploader.upload(image);
+                scheduledMessage.image = result.secure_url;
+            } else {
+                scheduledMessage.image = "";
+            }
+        }
+        
+        if (audio !== undefined) {
+            if (audio) {
+                const validation = validateAudioAttachment(audio);
+                if (!validation.isValid) {
+                    return res.status(400).json({ message: validation.error });
+                }
+                const result = await cloudinary.uploader.upload(audio, { resource_type: "auto" });
+                scheduledMessage.audio = result.secure_url;
+            } else {
+                scheduledMessage.audio = "";
+            }
+        }
+        
         if (replyTo !== undefined) scheduledMessage.replyTo = replyTo;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/controllers/scheduledMessage.controller.js` around lines 190 -
194, The updateScheduledMessage handler currently assigns message, image, and
audio directly and must be changed to reuse the same
sanitization/validation/upload flow used by scheduleMessage: when message is
provided run it through xss() before assigning to scheduledMessage.message; when
image or audio are provided validate them using the same attachment validation
function (the validator used by scheduleMessage), reject the update on
invalid/oversized/base64 data, and for valid attachments upload them to
Cloudinary using the same upload helper so scheduledMessage.image/audio store
the uploaded URL/metadata (not raw base64); ensure any helper returns used in
scheduleMessage are invoked here so stored and returned ScheduledMessage data
remain sanitized and validated.
🧹 Nitpick comments (1)
backend/src/lib/messageScheduler.js (1)

85-89: 💤 Low value

Consider using sanitizedMessage in push notification payload for consistency.

The notification body uses scheduledMsg.message (the raw DB value) instead of sanitizedMessage. While push notifications aren't rendered as HTML, using the sanitized version maintains consistency and prevents any edge cases if the notification is later displayed in an HTML context.

             const notificationPayload = {
                 title: "New message",
-                body: scheduledMsg.message || "(Message with attachment)",
+                body: sanitizedMessage || "(Message with attachment)",
                 icon: "/icon.png",
             };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/lib/messageScheduler.js` around lines 85 - 89, The push
notification payload currently sets body using scheduledMsg.message; update the
notificationPayload construction to use the already-computed sanitizedMessage
instead (replace scheduledMsg.message with sanitizedMessage) so the notification
body is consistent with sanitized output; locate this in messageScheduler.js
where notificationPayload is created and change the body field and any related
comment to reference sanitizedMessage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/src/lib/attachmentValidator.js`:
- Around line 8-12: The validators call .match() on their input and will throw
if base64Str is null/undefined or not a string; update validateImageAttachment
and validateAudioAttachment to defensively check that the input is a non-empty
string (e.g. if (typeof base64Str !== 'string' || !base64Str) return { isValid:
false, error: "Invalid file format structure or corrupt payload." }; ) before
performing the regex match so they return the same error object instead of
throwing a TypeError.

In `@backend/src/lib/messageScheduler.js`:
- Around line 27-29: The image/audio validation early-returns leave
scheduledMsg.status as "pending", causing permanent retries; update both
validation failure paths (the image validation branch around the check using
validation.isValid and the audio validation branch noted in the comment) to set
scheduledMsg.status = "failed" (or "cancelled" if your model uses that), set a
failureReason (e.g., validation.error or validation.error.message), and persist
the change (e.g., await scheduledMsg.save() or ScheduledMessage.updateOne({_id:
scheduledMsg._id}, {status: "failed", failureReason: ...})). Ensure both
branches mirror each other so failed validations are excluded from future
runScheduler runs. If the ScheduledMessage schema lacks status="failed" or
failureReason, update the schema accordingly.

---

Outside diff comments:
In `@backend/src/controllers/scheduledMessage.controller.js`:
- Around line 190-194: The updateScheduledMessage handler currently assigns
message, image, and audio directly and must be changed to reuse the same
sanitization/validation/upload flow used by scheduleMessage: when message is
provided run it through xss() before assigning to scheduledMessage.message; when
image or audio are provided validate them using the same attachment validation
function (the validator used by scheduleMessage), reject the update on
invalid/oversized/base64 data, and for valid attachments upload them to
Cloudinary using the same upload helper so scheduledMessage.image/audio store
the uploaded URL/metadata (not raw base64); ensure any helper returns used in
scheduleMessage are invoked here so stored and returned ScheduledMessage data
remain sanitized and validated.

---

Nitpick comments:
In `@backend/src/lib/messageScheduler.js`:
- Around line 85-89: The push notification payload currently sets body using
scheduledMsg.message; update the notificationPayload construction to use the
already-computed sanitizedMessage instead (replace scheduledMsg.message with
sanitizedMessage) so the notification body is consistent with sanitized output;
locate this in messageScheduler.js where notificationPayload is created and
change the body field and any related comment to reference sanitizedMessage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02ef6a5b-3e3e-47d7-968e-b24f2ef328bf

📥 Commits

Reviewing files that changed from the base of the PR and between b6ebd50 and 18c8049.

📒 Files selected for processing (3)
  • backend/src/controllers/scheduledMessage.controller.js
  • backend/src/lib/attachmentValidator.js
  • backend/src/lib/messageScheduler.js

Comment thread backend/src/lib/attachmentValidator.js
Comment thread backend/src/lib/messageScheduler.js
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.

bug: Scheduled messages bypass XSS sanitization and Cloudinary upload/validation applied to live messages

1 participant