@@ -220,6 +220,129 @@ describe('Regex Vulnerabilities', () => {
220220 } ) ;
221221 } ) ;
222222
223+ describe ( 'on authData id operator injection' , ( ) => {
224+ it ( 'should reject $regex operator in anonymous authData id on login' , async ( ) => {
225+ // Create a victim anonymous user with a known ID prefix
226+ const victimId = 'victim_' + Date . now ( ) ;
227+ const signupRes = await request ( {
228+ url : `${ serverURL } /users` ,
229+ method : 'POST' ,
230+ headers,
231+ body : JSON . stringify ( {
232+ ...keys ,
233+ _method : 'POST' ,
234+ authData : { anonymous : { id : victimId } } ,
235+ } ) ,
236+ } ) ;
237+ expect ( signupRes . data . objectId ) . toBeDefined ( ) ;
238+
239+ // Attacker tries to login with $regex to match the victim
240+ try {
241+ await request ( {
242+ url : `${ serverURL } /users` ,
243+ method : 'POST' ,
244+ headers,
245+ body : JSON . stringify ( {
246+ ...keys ,
247+ _method : 'POST' ,
248+ authData : { anonymous : { id : { $regex : '^victim_' } } } ,
249+ } ) ,
250+ } ) ;
251+ fail ( 'should not allow $regex in authData id' ) ;
252+ } catch ( e ) {
253+ expect ( e . data . code ) . toEqual ( Parse . Error . INVALID_VALUE ) ;
254+ }
255+ } ) ;
256+
257+ it ( 'should reject $ne operator in anonymous authData id on login' , async ( ) => {
258+ const victimId = 'victim_ne_' + Date . now ( ) ;
259+ await request ( {
260+ url : `${ serverURL } /users` ,
261+ method : 'POST' ,
262+ headers,
263+ body : JSON . stringify ( {
264+ ...keys ,
265+ _method : 'POST' ,
266+ authData : { anonymous : { id : victimId } } ,
267+ } ) ,
268+ } ) ;
269+
270+ try {
271+ await request ( {
272+ url : `${ serverURL } /users` ,
273+ method : 'POST' ,
274+ headers,
275+ body : JSON . stringify ( {
276+ ...keys ,
277+ _method : 'POST' ,
278+ authData : { anonymous : { id : { $ne : 'nonexistent' } } } ,
279+ } ) ,
280+ } ) ;
281+ fail ( 'should not allow $ne in authData id' ) ;
282+ } catch ( e ) {
283+ expect ( e . data . code ) . toEqual ( Parse . Error . INVALID_VALUE ) ;
284+ }
285+ } ) ;
286+
287+ it ( 'should reject $exists operator in anonymous authData id on login' , async ( ) => {
288+ const victimId = 'victim_exists_' + Date . now ( ) ;
289+ await request ( {
290+ url : `${ serverURL } /users` ,
291+ method : 'POST' ,
292+ headers,
293+ body : JSON . stringify ( {
294+ ...keys ,
295+ _method : 'POST' ,
296+ authData : { anonymous : { id : victimId } } ,
297+ } ) ,
298+ } ) ;
299+
300+ try {
301+ await request ( {
302+ url : `${ serverURL } /users` ,
303+ method : 'POST' ,
304+ headers,
305+ body : JSON . stringify ( {
306+ ...keys ,
307+ _method : 'POST' ,
308+ authData : { anonymous : { id : { $exists : true } } } ,
309+ } ) ,
310+ } ) ;
311+ fail ( 'should not allow $exists in authData id' ) ;
312+ } catch ( e ) {
313+ expect ( e . data . code ) . toEqual ( Parse . Error . INVALID_VALUE ) ;
314+ }
315+ } ) ;
316+
317+ it ( 'should allow valid string authData id for anonymous login' , async ( ) => {
318+ const userId = 'valid_anon_' + Date . now ( ) ;
319+ const signupRes = await request ( {
320+ url : `${ serverURL } /users` ,
321+ method : 'POST' ,
322+ headers,
323+ body : JSON . stringify ( {
324+ ...keys ,
325+ _method : 'POST' ,
326+ authData : { anonymous : { id : userId } } ,
327+ } ) ,
328+ } ) ;
329+ expect ( signupRes . data . objectId ) . toBeDefined ( ) ;
330+
331+ // Same ID should successfully log in
332+ const loginRes = await request ( {
333+ url : `${ serverURL } /users` ,
334+ method : 'POST' ,
335+ headers,
336+ body : JSON . stringify ( {
337+ ...keys ,
338+ _method : 'POST' ,
339+ authData : { anonymous : { id : userId } } ,
340+ } ) ,
341+ } ) ;
342+ expect ( loginRes . data . objectId ) . toEqual ( signupRes . data . objectId ) ;
343+ } ) ;
344+ } ) ;
345+
223346 describe ( 'on resend verification email' , ( ) => {
224347 // The PagesRouter uses express.urlencoded({ extended: false }) which does not parse
225348 // nested objects (e.g. token[$regex]=^.), so the HTTP layer already blocks object injection.
@@ -354,3 +477,44 @@ describe('Regex Vulnerabilities', () => {
354477 } ) ;
355478 } ) ;
356479} ) ;
480+
481+ describe ( 'Regex Vulnerabilities - authData operator injection with custom adapter' , ( ) => {
482+ it ( 'should reject non-string authData id for custom auth adapter on login' , async ( ) => {
483+ await reconfigureServer ( {
484+ auth : {
485+ myAdapter : {
486+ validateAuthData : ( ) => Promise . resolve ( ) ,
487+ validateAppId : ( ) => Promise . resolve ( ) ,
488+ } ,
489+ } ,
490+ } ) ;
491+
492+ const victimId = 'adapter_victim_' + Date . now ( ) ;
493+ await request ( {
494+ url : `${ serverURL } /users` ,
495+ method : 'POST' ,
496+ headers,
497+ body : JSON . stringify ( {
498+ ...keys ,
499+ _method : 'POST' ,
500+ authData : { myAdapter : { id : victimId , token : 'valid' } } ,
501+ } ) ,
502+ } ) ;
503+
504+ try {
505+ await request ( {
506+ url : `${ serverURL } /users` ,
507+ method : 'POST' ,
508+ headers,
509+ body : JSON . stringify ( {
510+ ...keys ,
511+ _method : 'POST' ,
512+ authData : { myAdapter : { id : { $regex : '^adapter_victim_' } , token : 'valid' } } ,
513+ } ) ,
514+ } ) ;
515+ fail ( 'should not allow $regex in custom adapter authData id' ) ;
516+ } catch ( e ) {
517+ expect ( e . data . code ) . toEqual ( Parse . Error . INVALID_VALUE ) ;
518+ }
519+ } ) ;
520+ } ) ;
0 commit comments