Skip to content

fix: Protected fields leak via LiveQuery afterEvent trigger (GHSA-5hmj-jcgp-6hff)#10232

Merged
mtrezza merged 2 commits intoparse-community:alphafrom
mtrezza:fix/GHSA-5hmj-jcgp-6hff-v9
Mar 17, 2026
Merged

fix: Protected fields leak via LiveQuery afterEvent trigger (GHSA-5hmj-jcgp-6hff)#10232
mtrezza merged 2 commits intoparse-community:alphafrom
mtrezza:fix/GHSA-5hmj-jcgp-6hff-v9

Conversation

@mtrezza
Copy link
Member

@mtrezza mtrezza commented Mar 17, 2026

Issue

Protected fields leak via LiveQuery afterEvent trigger (GHSA-5hmj-jcgp-6hff)

Tasks

  • Add new tests
  • Add changes to documentation (guides, migration guide)
  • Add entry to changelog
  • Security check
  • Benchmark check
  • Breaking change

@parse-github-assistant
Copy link

parse-github-assistant bot commented Mar 17, 2026

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Mar 17, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds a LiveQuery security test suite for protected fields and updates LiveQuery server handlers to propagate trigger-applied JSON transformations back into payloads before filtering and pushing to clients.

Changes

Cohort / File(s) Summary
LiveQuery Security Tests
spec/vulnerabilities.spec.js
New test suite "(GHSA-5hmj-jcgp-6hff) Protected fields leak via LiveQuery afterEvent trigger" validating that protected fields remain filtered across LiveQuery create/update/delete/enter/leave events when afterLiveQueryEvent triggers run.
LiveQuery Server Object Handling
src/LiveQuery/ParseLiveQueryServer.ts
Adjust _onAfterDelete and _onAfterSave to assign objects returned by toJSONwithObjects back into res.object/res.original and use those transformed values for subsequent filtering and client pushes.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant LiveQueryServer as LiveQuery Server
  participant Trigger as afterLiveQueryEvent Trigger
  participant Store as DataStore

  Client->>LiveQueryServer: Subscribe / receives event
  Store->>LiveQueryServer: Emit raw event (object)
  LiveQueryServer->>Trigger: run afterLiveQueryEvent(res)
  Trigger-->>LiveQueryServer: res.object / res.original (possibly transformed)
  LiveQueryServer->>LiveQueryServer: toJSONwithObjects + assign transformed object
  LiveQueryServer->>LiveQueryServer: apply data filtering (remove protected fields)
  LiveQueryServer-->>Client: push filtered event
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author; the description field is completely empty despite a template being available. Provide a detailed PR description following the template, including Issue, Approach, and relevant Tasks sections to document this security fix.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and specifically identifies the security vulnerability being fixed (GHSA-5hmj-jcgp-6hff) and the mechanism of the leak (LiveQuery afterEvent trigger), directly matching the test suite and code changes.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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[bot]
coderabbitai bot previously approved these changes Mar 17, 2026
@codecov
Copy link

codecov bot commented Mar 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.60%. Comparing base (a628911) to head (268227a).
⚠️ Report is 3 commits behind head on alpha.

Additional details and impacted files
@@           Coverage Diff           @@
##            alpha   #10232   +/-   ##
=======================================
  Coverage   92.60%   92.60%           
=======================================
  Files         192      192           
  Lines       16341    16347    +6     
  Branches      200      201    +1     
=======================================
+ Hits        15132    15138    +6     
  Misses       1192     1192           
  Partials       17       17           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 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.

Copy link

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

Caution

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

⚠️ Outside diff range comments (1)
src/LiveQuery/ParseLiveQueryServer.ts (1)

249-262: ⚠️ Potential issue | 🟠 Major

Keep the filtered payload request-scoped.

Line 261 and Lines 419-430 write a per-client filtered object back into deletedParseObject, currentParseObject, and originalParseObject, but those variables live outside requestIds.forEach(async ...). Once one restricted subscriber runs first, later subscribers can reuse that redacted copy and miss fields they are allowed to see.

💡 Suggested fix
-            if (res.object && typeof res.object.toJSON === 'function') {
-              deletedParseObject = toJSONwithObjects(res.object, res.object.className || className);
-            }
-            res.object = deletedParseObject;
+            let payloadObject = structuredClone(deletedParseObject);
+            if (res.object && typeof res.object.toJSON === 'function') {
+              payloadObject = toJSONwithObjects(res.object, res.object.className || className);
+            }
+            res.object = payloadObject;
             await this._filterSensitiveData(
               classLevelPermissions,
               res,
               client,
               requestId,
               op,
               subscription.query
             );
-            deletedParseObject = res.object;
-            client.pushDelete(requestId, deletedParseObject);
+            payloadObject = res.object;
+            client.pushDelete(requestId, payloadObject);
-            if (res.object && typeof res.object.toJSON === 'function') {
-              currentParseObject = toJSONwithObjects(res.object, res.object.className || className);
-            }
-            if (res.original && typeof res.original.toJSON === 'function') {
-              originalParseObject = toJSONwithObjects(
-                res.original,
-                res.original.className || className
-              );
-            }
-            res.object = currentParseObject;
-            res.original = originalParseObject;
+            let payloadObject = structuredClone(currentParseObject);
+            let payloadOriginal = structuredClone(originalParseObject);
+            if (res.object && typeof res.object.toJSON === 'function') {
+              payloadObject = toJSONwithObjects(res.object, res.object.className || className);
+            }
+            if (res.original && typeof res.original.toJSON === 'function') {
+              payloadOriginal = toJSONwithObjects(
+                res.original,
+                res.original.className || className
+              );
+            }
+            res.object = payloadObject;
+            res.original = payloadOriginal;
             await this._filterSensitiveData(
               classLevelPermissions,
               res,
               client,
               requestId,
               op,
               subscription.query
             );
-            currentParseObject = res.object;
-            originalParseObject = res.original ?? null;
+            payloadObject = res.object;
+            payloadOriginal = res.original ?? null;
             const functionName = 'push' + res.event.charAt(0).toUpperCase() + res.event.slice(1);
             if (client[functionName]) {
-              client[functionName](requestId, currentParseObject, originalParseObject);
+              client[functionName](requestId, payloadObject, payloadOriginal);
             }

Also applies to: 410-433

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/LiveQuery/ParseLiveQueryServer.ts` around lines 249 - 262, The code is
mutating outer-scope variables (deletedParseObject, currentParseObject,
originalParseObject) with the per-client filtered result from
_filterSensitiveData, causing subsequent subscribers to see an already-redacted
object; instead, create a request-scoped variable (e.g., filteredDeleted,
filteredCurrent, filteredOriginal) inside the requestIds.forEach/async handler,
pass that into _filterSensitiveData (or clone res.object into a local var before
filtering), and use those local filtered values when calling client.pushDelete /
client.pushCreate / client.pushUpdate so the outer originals remain unchanged
for other subscribers; update all places referencing
deletedParseObject/currentParseObject/originalParseObject within the subscriber
loop to use the local filtered* variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/LiveQuery/ParseLiveQueryServer.ts`:
- Around line 249-262: The code is mutating outer-scope variables
(deletedParseObject, currentParseObject, originalParseObject) with the
per-client filtered result from _filterSensitiveData, causing subsequent
subscribers to see an already-redacted object; instead, create a request-scoped
variable (e.g., filteredDeleted, filteredCurrent, filteredOriginal) inside the
requestIds.forEach/async handler, pass that into _filterSensitiveData (or clone
res.object into a local var before filtering), and use those local filtered
values when calling client.pushDelete / client.pushCreate / client.pushUpdate so
the outer originals remain unchanged for other subscribers; update all places
referencing deletedParseObject/currentParseObject/originalParseObject within the
subscriber loop to use the local filtered* variables.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6c29a206-9123-40da-b14f-2f5c6381b3dc

📥 Commits

Reviewing files that changed from the base of the PR and between 9cd5bbd and 268227a.

📒 Files selected for processing (1)
  • src/LiveQuery/ParseLiveQueryServer.ts

@mtrezza mtrezza changed the title fix: GHSA-5hmj-jcgp-6hff v9 fix: Protected fields leak via LiveQuery afterEvent trigger (GHSA-5hmj-jcgp-6hff) Mar 17, 2026
@mtrezza mtrezza merged commit 6648500 into parse-community:alpha Mar 17, 2026
22 of 24 checks passed
parseplatformorg pushed a commit that referenced this pull request Mar 17, 2026
# [9.6.0-alpha.35](9.6.0-alpha.34...9.6.0-alpha.35) (2026-03-17)

### Bug Fixes

* Protected fields leak via LiveQuery afterEvent trigger ([GHSA-5hmj-jcgp-6hff](GHSA-5hmj-jcgp-6hff)) ([#10232](#10232)) ([6648500](6648500))
@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 9.6.0-alpha.35

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state:released-alpha Released as alpha version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants