Skip to content

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

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

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

Conversation

@harrshita123

@harrshita123 harrshita123 commented Jun 16, 2026

Copy link
Copy Markdown

This PR replaces #584, which could not be reopened because the submitting fork repository was deleted and recreated.

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

Release Notes

  • New Features

    • Scheduled messages now support attaching images and audio files with automatic validation
    • Failed scheduled messages now include detailed failure reasons for easier troubleshooting
  • Bug Fixes

    • Enhanced input sanitization for scheduled message content to improve security
    • Added file size limits and format validation for attached media

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR closes a security gap where scheduled messages bypassed XSS sanitization, attachment MIME/size validation, and Cloudinary upload. A new attachmentValidator.js provides shared Base64 validators. The ScheduledMessage schema gains a failed status and failureReason field. Both the schedule/update controller and the background scheduler delivery path now sanitize text with xss and validate/upload attachments via Cloudinary.

Changes

Scheduled Message Security: XSS Sanitization & Cloudinary Validation

Layer / File(s) Summary
Schema extension and shared attachment validators
backend/src/models/scheduledMessage.model.js, backend/src/lib/attachmentValidator.js
scheduledMessageSchema gains a "failed" enum value for status and a new failureReason field. validateImageAttachment (5 MB, JPEG/PNG/WEBP/GIF) and validateAudioAttachment (10 MB, WEBM/MP3/WAV/MPEG/OGG/M4A) are added as shared helpers that verify Base64 data-URL structure, MIME type, and estimated decoded size.
Controller: sanitization and Cloudinary upload at schedule/update time
backend/src/controllers/scheduledMessage.controller.js
Imports xss, cloudinary, and the new validators. scheduleMessage adds receiver ID format check, future-timestamp enforcement, at-least-one-content requirement, message sanitization, and validate-then-upload flows for image/audio. updateScheduledMessage replaces direct field assignment with sanitization for message and validate/upload-or-clear for image/audio.
Scheduler: sanitization, Cloudinary upload, and failure handling at delivery time
backend/src/lib/messageScheduler.js
Imports xss, cloudinary, and validators. processScheduledMessage sanitizes message text, validates and uploads data:-URI attachments to Cloudinary, sets status = "failed" with failureReason on validation errors, and constructs the delivered Message with sanitized text and Cloudinary secure URLs. Push notification body uses sanitized text.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 A rabbit once read a message most foul,
With scripts in the base64, making him growl.
Now xss() scrubs every line squeaky clean,
And Cloudinary handles each image unseen.
Failed states leave a trace, failureReason in tow —
The scheduled path's safe, from hop to hello! 🌟

🚥 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 title accurately describes the main change: enforcing XSS sanitization and Cloudinary validation for scheduled messages, which directly addresses the primary security and media-handling defects documented in issue #563.
Linked Issues check ✅ Passed The PR fully addresses all coding requirements from issue #563: adds XSS sanitization via xss() function, enforces MIME-type and size validation for attachments, uploads attachments to Cloudinary, factors validation into a shared module, and applies these controls at schedule and delivery time.
Out of Scope Changes check ✅ Passed All changes are directly scoped to resolving issue #563: scheduledMessage controller adds sanitization and validation, attachmentValidator provides shared validation helpers, messageScheduler applies sanitization and Cloudinary uploads, and scheduledMessage model supports failure tracking—all core to the security fix.
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.

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)

99-101: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Status filter excludes the new "failed" status.

The schema now includes "failed" as a valid status (added in scheduledMessage.model.js line 11), but this filter array doesn't include it. Users cannot query their failed scheduled messages.

🐛 Proposed fix
-        if (status && ["pending", "sent", "cancelled"].includes(status)) {
+        if (status && ["pending", "sent", "cancelled", "failed"].includes(status)) {
             query.status = status;
         }
🤖 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 99 -
101, The status filter array in the if statement (which checks if the status is
in ["pending", "sent", "cancelled"]) does not include the new "failed" status
that was added to the schema in scheduledMessage.model.js. Add "failed" to this
status array so that users can query their failed scheduled messages. Update the
array to include all four valid statuses: "pending", "sent", "cancelled", and
"failed".
🧹 Nitpick comments (3)
backend/src/lib/attachmentValidator.js (2)

8-27: 💤 Low value

Error message hardcodes size limit despite configurable parameter.

Line 24 hardcodes "5MB" but maxSizeBytes is a parameter. If a caller passes a different limit, the error message will be misleading.

💡 Suggested fix
-    if (binarySizeEstimate > maxSizeBytes) {
-        return { isValid: false, error: "File boundary limit exceeded. Image size must be under 5MB." };
-    }
+    if (binarySizeEstimate > maxSizeBytes) {
+        const maxMB = (maxSizeBytes / (1024 * 1024)).toFixed(0);
+        return { isValid: false, error: `File boundary limit exceeded. Image size must be under ${maxMB}MB.` };
+    }
🤖 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/attachmentValidator.js` around lines 8 - 27, The error
message in the validateImageAttachment function hardcodes "5MB" as the file size
limit, but the function accepts a configurable maxSizeBytes parameter. When the
file size check fails, calculate the actual limit in megabytes from the
maxSizeBytes parameter and include that dynamic value in the error message
instead of hardcoding "5MB", so the error message accurately reflects the actual
limit enforced for each function call.

29-54: 💤 Low value

Same hardcoded limit issue for audio validation.

Line 51 hardcodes "10MB" but maxSizeBytes is configurable. Apply the same fix as for image validation.

🤖 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/attachmentValidator.js` around lines 29 - 54, The
validateAudioAttachment function has a hardcoded "10MB" string in its error
message when the audio size check fails, but the maxSizeBytes parameter is
configurable. Replace the hardcoded "10MB" limit text in the error message with
a dynamic value calculated from the maxSizeBytes parameter by converting bytes
to megabytes, so the error message accurately reflects whatever size limit is
actually being enforced.
backend/src/lib/messageScheduler.js (1)

91-106: ⚖️ Poor tradeoff

Unhandled errors leave message in pending state indefinitely.

The outer catch (lines 104-106) logs the error but doesn't update the message status. While this allows retries for transient failures (e.g., network issues), permanent failures (e.g., Cloudinary credential issues) would cause infinite retry loops every 30 seconds.

Consider adding retry limits or marking as failed after repeated attempts.

🤖 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 91 - 106, The outer catch
block logs errors from processing the scheduled message but doesn't update the
message status, causing permanent failures to retry indefinitely every 30
seconds. Add retry limit logic in the outer catch block: track the number of
retry attempts (consider adding a retryCount field to scheduledMsg), and after a
maximum number of retries (e.g., 5 attempts), update the scheduledMsg status to
"failed" using the same database update pattern used elsewhere in the code,
rather than leaving it in "pending" state indefinitely. For transient failures
within the retry limit, the message should continue to be retried on the next
scheduler cycle.
🤖 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.

Outside diff comments:
In `@backend/src/controllers/scheduledMessage.controller.js`:
- Around line 99-101: The status filter array in the if statement (which checks
if the status is in ["pending", "sent", "cancelled"]) does not include the new
"failed" status that was added to the schema in scheduledMessage.model.js. Add
"failed" to this status array so that users can query their failed scheduled
messages. Update the array to include all four valid statuses: "pending",
"sent", "cancelled", and "failed".

---

Nitpick comments:
In `@backend/src/lib/attachmentValidator.js`:
- Around line 8-27: The error message in the validateImageAttachment function
hardcodes "5MB" as the file size limit, but the function accepts a configurable
maxSizeBytes parameter. When the file size check fails, calculate the actual
limit in megabytes from the maxSizeBytes parameter and include that dynamic
value in the error message instead of hardcoding "5MB", so the error message
accurately reflects the actual limit enforced for each function call.
- Around line 29-54: The validateAudioAttachment function has a hardcoded "10MB"
string in its error message when the audio size check fails, but the
maxSizeBytes parameter is configurable. Replace the hardcoded "10MB" limit text
in the error message with a dynamic value calculated from the maxSizeBytes
parameter by converting bytes to megabytes, so the error message accurately
reflects whatever size limit is actually being enforced.

In `@backend/src/lib/messageScheduler.js`:
- Around line 91-106: The outer catch block logs errors from processing the
scheduled message but doesn't update the message status, causing permanent
failures to retry indefinitely every 30 seconds. Add retry limit logic in the
outer catch block: track the number of retry attempts (consider adding a
retryCount field to scheduledMsg), and after a maximum number of retries (e.g.,
5 attempts), update the scheduledMsg status to "failed" using the same database
update pattern used elsewhere in the code, rather than leaving it in "pending"
state indefinitely. For transient failures within the retry limit, the message
should continue to be retried on the next scheduler cycle.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40bd8a44-6e74-4a40-bdf4-932f5e1be0c3

📥 Commits

Reviewing files that changed from the base of the PR and between b6ebd50 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

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