@@ -1706,6 +1706,102 @@ describe('(GHSA-r2m8-pxm9-9c4g) Protected fields WHERE clause bypass via dot-not
17061706 } ) ;
17071707} ) ;
17081708
1709+ describe ( '(GHSA-j7mm-f4rv-6q6q) Protected fields bypass via LiveQuery dot-notation WHERE' , ( ) => {
1710+ let obj ;
1711+
1712+ beforeEach ( async ( ) => {
1713+ Parse . CoreManager . getLiveQueryController ( ) . setDefaultLiveQueryClient ( null ) ;
1714+ await reconfigureServer ( {
1715+ liveQuery : { classNames : [ 'SecretClass' ] } ,
1716+ startLiveQueryServer : true ,
1717+ verbose : false ,
1718+ silent : true ,
1719+ } ) ;
1720+ const config = Config . get ( Parse . applicationId ) ;
1721+ const schemaController = await config . database . loadSchema ( ) ;
1722+ await schemaController . addClassIfNotExists (
1723+ 'SecretClass' ,
1724+ { secretObj : { type : 'Object' } , publicField : { type : 'String' } } ,
1725+ ) ;
1726+ await schemaController . updateClass (
1727+ 'SecretClass' ,
1728+ { } ,
1729+ {
1730+ find : { '*' : true } ,
1731+ get : { '*' : true } ,
1732+ create : { '*' : true } ,
1733+ update : { '*' : true } ,
1734+ delete : { '*' : true } ,
1735+ addField : { } ,
1736+ protectedFields : { '*' : [ 'secretObj' ] } ,
1737+ }
1738+ ) ;
1739+
1740+ obj = new Parse . Object ( 'SecretClass' ) ;
1741+ obj . set ( 'secretObj' , { apiKey : 'SENSITIVE_KEY_123' , score : 42 } ) ;
1742+ obj . set ( 'publicField' , 'visible' ) ;
1743+ await obj . save ( null , { useMasterKey : true } ) ;
1744+ } ) ;
1745+
1746+ afterEach ( async ( ) => {
1747+ const client = await Parse . CoreManager . getLiveQueryController ( ) . getDefaultLiveQueryClient ( ) ;
1748+ await client . close ( ) ;
1749+ } ) ;
1750+
1751+ it ( 'should reject LiveQuery subscription with dot-notation on protected field in where clause' , async ( ) => {
1752+ const query = new Parse . Query ( 'SecretClass' ) ;
1753+ query . _addCondition ( 'secretObj.apiKey' , '$eq' , 'SENSITIVE_KEY_123' ) ;
1754+ await expectAsync ( query . subscribe ( ) ) . toBeRejected ( ) ;
1755+ } ) ;
1756+
1757+ it ( 'should reject LiveQuery subscription with protected field directly in where clause' , async ( ) => {
1758+ const query = new Parse . Query ( 'SecretClass' ) ;
1759+ query . exists ( 'secretObj' ) ;
1760+ await expectAsync ( query . subscribe ( ) ) . toBeRejected ( ) ;
1761+ } ) ;
1762+
1763+ it ( 'should reject LiveQuery subscription with protected field in $or' , async ( ) => {
1764+ const q1 = new Parse . Query ( 'SecretClass' ) ;
1765+ q1 . _addCondition ( 'secretObj.apiKey' , '$eq' , 'SENSITIVE_KEY_123' ) ;
1766+ const q2 = new Parse . Query ( 'SecretClass' ) ;
1767+ q2 . _addCondition ( 'secretObj.apiKey' , '$eq' , 'other' ) ;
1768+ const query = Parse . Query . or ( q1 , q2 ) ;
1769+ await expectAsync ( query . subscribe ( ) ) . toBeRejected ( ) ;
1770+ } ) ;
1771+
1772+ it ( 'should reject LiveQuery subscription with $regex on protected field (boolean oracle)' , async ( ) => {
1773+ const query = new Parse . Query ( 'SecretClass' ) ;
1774+ query . _addCondition ( 'secretObj.apiKey' , '$regex' , '^S' ) ;
1775+ await expectAsync ( query . subscribe ( ) ) . toBeRejected ( ) ;
1776+ } ) ;
1777+
1778+ it ( 'should reject LiveQuery subscription with deeply nested dot-notation on protected field' , async ( ) => {
1779+ const query = new Parse . Query ( 'SecretClass' ) ;
1780+ query . _addCondition ( 'secretObj.nested.deep.key' , '$eq' , 'value' ) ;
1781+ await expectAsync ( query . subscribe ( ) ) . toBeRejected ( ) ;
1782+ } ) ;
1783+
1784+ it ( 'should allow LiveQuery subscription on non-protected fields and strip protected fields from response' , async ( ) => {
1785+ const query = new Parse . Query ( 'SecretClass' ) ;
1786+ query . exists ( 'publicField' ) ;
1787+ const subscription = await query . subscribe ( ) ;
1788+ await Promise . all ( [
1789+ new Promise ( resolve => {
1790+ subscription . on ( 'update' , object => {
1791+ expect ( object . get ( 'secretObj' ) ) . toBeUndefined ( ) ;
1792+ expect ( object . get ( 'publicField' ) ) . toBe ( 'updated' ) ;
1793+ resolve ( ) ;
1794+ } ) ;
1795+ } ) ,
1796+ obj . save ( { publicField : 'updated' } , { useMasterKey : true } ) ,
1797+ ] ) ;
1798+ } ) ;
1799+
1800+ // Note: master key bypass is inherently tested by the `!client.hasMasterKey` guard
1801+ // in the implementation. Testing master key LiveQuery requires configuring keyPairs
1802+ // in the LiveQuery server config, which is not part of the default test setup.
1803+ } ) ;
1804+
17091805describe ( '(GHSA-w54v-hf9p-8856) User enumeration via email verification endpoint' , ( ) => {
17101806 let sendVerificationEmail ;
17111807
0 commit comments