@@ -653,6 +653,80 @@ describe('Parse.File testing', () => {
653653 done ( ) ;
654654 } ) ;
655655 } ) ;
656+
657+ describe ( 'URI-backed file upload is disabled to prevent SSRF attack' , ( ) => {
658+ const express = require ( 'express' ) ;
659+ let testServer ;
660+ let testServerPort ;
661+ let requestsMade ;
662+
663+ beforeEach ( async ( ) => {
664+ requestsMade = [ ] ;
665+ const app = express ( ) ;
666+ app . use ( ( req , res ) => {
667+ requestsMade . push ( { url : req . url , method : req . method } ) ;
668+ res . status ( 200 ) . send ( 'test file content' ) ;
669+ } ) ;
670+ testServer = app . listen ( 0 ) ;
671+ testServerPort = testServer . address ( ) . port ;
672+ } ) ;
673+
674+ afterEach ( async ( ) => {
675+ if ( testServer ) {
676+ await new Promise ( resolve => testServer . close ( resolve ) ) ;
677+ }
678+ Parse . Cloud . _removeAllHooks ( ) ;
679+ } ) ;
680+
681+ it ( 'does not access URI when file upload attempted over REST' , async ( ) => {
682+ const response = await request ( {
683+ method : 'POST' ,
684+ url : 'http://localhost:8378/1/classes/TestClass' ,
685+ headers : {
686+ 'Content-Type' : 'application/json' ,
687+ 'X-Parse-Application-Id' : 'test' ,
688+ 'X-Parse-REST-API-Key' : 'rest' ,
689+ } ,
690+ body : {
691+ file : {
692+ __type : 'File' ,
693+ name : 'test.txt' ,
694+ _source : {
695+ format : 'uri' ,
696+ uri : `http://127.0.0.1:${ testServerPort } /secret-file.txt` ,
697+ } ,
698+ } ,
699+ } ,
700+ } ) ;
701+ expect ( response . status ) . toBe ( 201 ) ;
702+ // Verify no HTTP request was made to the URI
703+ expect ( requestsMade . length ) . toBe ( 0 ) ;
704+ } ) ;
705+
706+ it ( 'does not access URI when file created in beforeSave trigger' , async ( ) => {
707+ Parse . Cloud . beforeSave ( Parse . File , ( ) => {
708+ return new Parse . File ( 'trigger-file.txt' , {
709+ uri : `http://127.0.0.1:${ testServerPort } /secret-file.txt` ,
710+ } ) ;
711+ } ) ;
712+ await expectAsync (
713+ request ( {
714+ method : 'POST' ,
715+ headers : {
716+ 'Content-Type' : 'application/octet-stream' ,
717+ 'X-Parse-Application-Id' : 'test' ,
718+ 'X-Parse-REST-API-Key' : 'rest' ,
719+ } ,
720+ url : 'http://localhost:8378/1/files/test.txt' ,
721+ body : 'test content' ,
722+ } )
723+ ) . toBeRejectedWith ( jasmine . objectContaining ( {
724+ status : 400
725+ } ) ) ;
726+ // Verify no HTTP request was made to the URI
727+ expect ( requestsMade . length ) . toBe ( 0 ) ;
728+ } ) ;
729+ } ) ;
656730 } ) ;
657731
658732 describe ( 'deleting files' , ( ) => {
0 commit comments