@@ -2769,3 +2769,103 @@ describe('(GHSA-9xp9-j92r-p88v) Stack overflow process crash via deeply nested q
27692769 ) ;
27702770 } ) ;
27712771} ) ;
2772+
2773+ describe ( '(GHSA-fjxm-vhvc-gcmj) LiveQuery Operator Type Confusion' , ( ) => {
2774+ const matchesQuery = require ( '../lib/LiveQuery/QueryTools' ) . matchesQuery ;
2775+
2776+ // Unit tests: matchesQuery receives the raw where clause (not {className, where})
2777+ // just as _matchesSubscription passes subscription.query (the where clause)
2778+ describe ( 'matchesQuery with type-confused operators' , ( ) => {
2779+ it ( '$in with object instead of array throws' , ( ) => {
2780+ const object = { className : 'TestObject' , objectId : 'obj1' , name : 'abc' } ;
2781+ const where = { name : { $in : { x : 1 } } } ;
2782+ expect ( ( ) => matchesQuery ( object , where ) ) . toThrow ( ) ;
2783+ } ) ;
2784+
2785+ it ( '$nin with object instead of array throws' , ( ) => {
2786+ const object = { className : 'TestObject' , objectId : 'obj1' , name : 'abc' } ;
2787+ const where = { name : { $nin : { x : 1 } } } ;
2788+ expect ( ( ) => matchesQuery ( object , where ) ) . toThrow ( ) ;
2789+ } ) ;
2790+
2791+ it ( '$containedBy with object instead of array throws' , ( ) => {
2792+ const object = { className : 'TestObject' , objectId : 'obj1' , name : [ 'abc' ] } ;
2793+ const where = { name : { $containedBy : { x : 1 } } } ;
2794+ expect ( ( ) => matchesQuery ( object , where ) ) . toThrow ( ) ;
2795+ } ) ;
2796+
2797+ it ( '$containedBy with missing field throws' , ( ) => {
2798+ const object = { className : 'TestObject' , objectId : 'obj1' } ;
2799+ const where = { name : { $containedBy : [ 'abc' , 'xyz' ] } } ;
2800+ expect ( ( ) => matchesQuery ( object , where ) ) . toThrow ( ) ;
2801+ } ) ;
2802+
2803+ it ( '$all with object field value throws' , ( ) => {
2804+ const object = { className : 'TestObject' , objectId : 'obj1' , name : { x : 1 } } ;
2805+ const where = { name : { $all : [ 'abc' ] } } ;
2806+ expect ( ( ) => matchesQuery ( object , where ) ) . toThrow ( ) ;
2807+ } ) ;
2808+
2809+ it ( '$in with valid array does not throw' , ( ) => {
2810+ const object = { className : 'TestObject' , objectId : 'obj1' , name : 'abc' } ;
2811+ const where = { name : { $in : [ 'abc' , 'xyz' ] } } ;
2812+ expect ( ( ) => matchesQuery ( object , where ) ) . not . toThrow ( ) ;
2813+ expect ( matchesQuery ( object , where ) ) . toBe ( true ) ;
2814+ } ) ;
2815+ } ) ;
2816+
2817+ // Integration test: verify that a LiveQuery subscription with type-confused
2818+ // operators does not crash the server and other subscriptions continue working
2819+ describe ( 'LiveQuery integration' , ( ) => {
2820+ beforeEach ( async ( ) => {
2821+ Parse . CoreManager . getLiveQueryController ( ) . setDefaultLiveQueryClient ( null ) ;
2822+ await reconfigureServer ( {
2823+ liveQuery : { classNames : [ 'TestObject' ] } ,
2824+ startLiveQueryServer : true ,
2825+ verbose : false ,
2826+ silent : true ,
2827+ } ) ;
2828+ } ) ;
2829+
2830+ afterEach ( async ( ) => {
2831+ const client = await Parse . CoreManager . getLiveQueryController ( ) . getDefaultLiveQueryClient ( ) ;
2832+ if ( client ) {
2833+ await client . close ( ) ;
2834+ }
2835+ } ) ;
2836+
2837+ it ( 'server does not crash and other subscriptions work when type-confused subscription exists' , async ( ) => {
2838+ // First subscribe with a malformed query via manual client
2839+ const malClient = new Parse . LiveQueryClient ( {
2840+ applicationId : 'test' ,
2841+ serverURL : 'ws://localhost:1337' ,
2842+ javascriptKey : 'test' ,
2843+ } ) ;
2844+ malClient . open ( ) ;
2845+ const malformedQuery = new Parse . Query ( 'TestObject' ) ;
2846+ malformedQuery . _where = { name : { $in : { x : 1 } } } ;
2847+ await malClient . subscribe ( malformedQuery ) ;
2848+
2849+ // Then subscribe with a valid query using the default client
2850+ const validQuery = new Parse . Query ( 'TestObject' ) ;
2851+ validQuery . equalTo ( 'name' , 'test' ) ;
2852+ const validSubscription = await validQuery . subscribe ( ) ;
2853+
2854+ try {
2855+ const createPromise = new Promise ( resolve => {
2856+ validSubscription . on ( 'create' , object => {
2857+ expect ( object . get ( 'name' ) ) . toBe ( 'test' ) ;
2858+ resolve ( ) ;
2859+ } ) ;
2860+ } ) ;
2861+
2862+ const obj = new Parse . Object ( 'TestObject' ) ;
2863+ obj . set ( 'name' , 'test' ) ;
2864+ await obj . save ( ) ;
2865+ await createPromise ;
2866+ } finally {
2867+ malClient . close ( ) ;
2868+ }
2869+ } ) ;
2870+ } ) ;
2871+ } ) ;
0 commit comments