@@ -3650,3 +3650,104 @@ describe('(GHSA-fph2-r4qg-9576) LiveQuery bypasses CLP pointer permission enforc
36503650 expect ( createSpyC ) . not . toHaveBeenCalled ( ) ;
36513651 } ) ;
36523652} ) ;
3653+
3654+ describe ( '(GHSA-qpc3-fg4j-8hgm) Protected field change detection oracle via LiveQuery watch parameter' , ( ) => {
3655+ const { sleep } = require ( '../lib/TestUtils' ) ;
3656+ let obj ;
3657+
3658+ beforeEach ( async ( ) => {
3659+ Parse . CoreManager . getLiveQueryController ( ) . setDefaultLiveQueryClient ( null ) ;
3660+ await reconfigureServer ( {
3661+ liveQuery : { classNames : [ 'SecretClass' ] } ,
3662+ startLiveQueryServer : true ,
3663+ verbose : false ,
3664+ silent : true ,
3665+ } ) ;
3666+ const config = Config . get ( Parse . applicationId ) ;
3667+ const schemaController = await config . database . loadSchema ( ) ;
3668+ await schemaController . addClassIfNotExists ( 'SecretClass' , {
3669+ secretObj : { type : 'Object' } ,
3670+ publicField : { type : 'String' } ,
3671+ } ) ;
3672+ await schemaController . updateClass (
3673+ 'SecretClass' ,
3674+ { } ,
3675+ {
3676+ find : { '*' : true } ,
3677+ get : { '*' : true } ,
3678+ create : { '*' : true } ,
3679+ update : { '*' : true } ,
3680+ delete : { '*' : true } ,
3681+ addField : { } ,
3682+ protectedFields : { '*' : [ 'secretObj' ] } ,
3683+ }
3684+ ) ;
3685+
3686+ obj = new Parse . Object ( 'SecretClass' ) ;
3687+ obj . set ( 'secretObj' , { apiKey : 'SENSITIVE_KEY_123' , score : 42 } ) ;
3688+ obj . set ( 'publicField' , 'visible' ) ;
3689+ await obj . save ( null , { useMasterKey : true } ) ;
3690+ } ) ;
3691+
3692+ afterEach ( async ( ) => {
3693+ const client = await Parse . CoreManager . getLiveQueryController ( ) . getDefaultLiveQueryClient ( ) ;
3694+ if ( client ) {
3695+ await client . close ( ) ;
3696+ }
3697+ } ) ;
3698+
3699+ it ( 'should reject LiveQuery subscription with protected field in watch' , async ( ) => {
3700+ const query = new Parse . Query ( 'SecretClass' ) ;
3701+ query . watch ( 'secretObj' ) ;
3702+ await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
3703+ new Parse . Error ( Parse . Error . OPERATION_FORBIDDEN , 'Permission denied' )
3704+ ) ;
3705+ } ) ;
3706+
3707+ it ( 'should reject LiveQuery subscription with dot-notation on protected field in watch' , async ( ) => {
3708+ const query = new Parse . Query ( 'SecretClass' ) ;
3709+ query . watch ( 'secretObj.apiKey' ) ;
3710+ await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
3711+ new Parse . Error ( Parse . Error . OPERATION_FORBIDDEN , 'Permission denied' )
3712+ ) ;
3713+ } ) ;
3714+
3715+ it ( 'should reject LiveQuery subscription with deeply nested dot-notation on protected field in watch' , async ( ) => {
3716+ const query = new Parse . Query ( 'SecretClass' ) ;
3717+ query . watch ( 'secretObj.nested.deep.key' ) ;
3718+ await expectAsync ( query . subscribe ( ) ) . toBeRejectedWith (
3719+ new Parse . Error ( Parse . Error . OPERATION_FORBIDDEN , 'Permission denied' )
3720+ ) ;
3721+ } ) ;
3722+
3723+ it ( 'should allow LiveQuery subscription with non-protected field in watch' , async ( ) => {
3724+ const query = new Parse . Query ( 'SecretClass' ) ;
3725+ query . watch ( 'publicField' ) ;
3726+ const subscription = await query . subscribe ( ) ;
3727+ await Promise . all ( [
3728+ new Promise ( resolve => {
3729+ subscription . on ( 'update' , object => {
3730+ expect ( object . get ( 'secretObj' ) ) . toBeUndefined ( ) ;
3731+ expect ( object . get ( 'publicField' ) ) . toBe ( 'updated' ) ;
3732+ resolve ( ) ;
3733+ } ) ;
3734+ } ) ,
3735+ obj . save ( { publicField : 'updated' } , { useMasterKey : true } ) ,
3736+ ] ) ;
3737+ } ) ;
3738+
3739+ it ( 'should not deliver update event when only non-watched field changes' , async ( ) => {
3740+ const query = new Parse . Query ( 'SecretClass' ) ;
3741+ query . watch ( 'publicField' ) ;
3742+ const subscription = await query . subscribe ( ) ;
3743+ const updateSpy = jasmine . createSpy ( 'update' ) ;
3744+ subscription . on ( 'update' , updateSpy ) ;
3745+
3746+ // Change a field that is NOT in the watch list
3747+ obj . set ( 'secretObj' , { apiKey : 'ROTATED_KEY' , score : 99 } ) ;
3748+ await obj . save ( null , { useMasterKey : true } ) ;
3749+ await sleep ( 500 ) ;
3750+ expect ( updateSpy ) . not . toHaveBeenCalled ( ) ;
3751+ } ) ;
3752+
3753+ } ) ;
0 commit comments