@@ -633,6 +633,80 @@ describe('Parse.File testing', () => {
633633 done ( ) ;
634634 } ) ;
635635 } ) ;
636+
637+ describe ( 'URI-backed file upload is disabled to prevent SSRF attack' , ( ) => {
638+ const express = require ( 'express' ) ;
639+ let testServer ;
640+ let testServerPort ;
641+ let requestsMade ;
642+
643+ beforeEach ( async ( ) => {
644+ requestsMade = [ ] ;
645+ const app = express ( ) ;
646+ app . use ( ( req , res ) => {
647+ requestsMade . push ( { url : req . url , method : req . method } ) ;
648+ res . status ( 200 ) . send ( 'test file content' ) ;
649+ } ) ;
650+ testServer = app . listen ( 0 ) ;
651+ testServerPort = testServer . address ( ) . port ;
652+ } ) ;
653+
654+ afterEach ( async ( ) => {
655+ if ( testServer ) {
656+ await new Promise ( resolve => testServer . close ( resolve ) ) ;
657+ }
658+ Parse . Cloud . _removeAllHooks ( ) ;
659+ } ) ;
660+
661+ it ( 'does not access URI when file upload attempted over REST' , async ( ) => {
662+ const response = await request ( {
663+ method : 'POST' ,
664+ url : 'http://localhost:8378/1/classes/TestClass' ,
665+ headers : {
666+ 'Content-Type' : 'application/json' ,
667+ 'X-Parse-Application-Id' : 'test' ,
668+ 'X-Parse-REST-API-Key' : 'rest' ,
669+ } ,
670+ body : {
671+ file : {
672+ __type : 'File' ,
673+ name : 'test.txt' ,
674+ _source : {
675+ format : 'uri' ,
676+ uri : `http://127.0.0.1:${ testServerPort } /secret-file.txt` ,
677+ } ,
678+ } ,
679+ } ,
680+ } ) ;
681+ expect ( response . status ) . toBe ( 201 ) ;
682+ // Verify no HTTP request was made to the URI
683+ expect ( requestsMade . length ) . toBe ( 0 ) ;
684+ } ) ;
685+
686+ it ( 'does not access URI when file created in beforeSave trigger' , async ( ) => {
687+ Parse . Cloud . beforeSave ( Parse . File , ( ) => {
688+ return new Parse . File ( 'trigger-file.txt' , {
689+ uri : `http://127.0.0.1:${ testServerPort } /secret-file.txt` ,
690+ } ) ;
691+ } ) ;
692+ await expectAsync (
693+ request ( {
694+ method : 'POST' ,
695+ headers : {
696+ 'Content-Type' : 'application/octet-stream' ,
697+ 'X-Parse-Application-Id' : 'test' ,
698+ 'X-Parse-REST-API-Key' : 'rest' ,
699+ } ,
700+ url : 'http://localhost:8378/1/files/test.txt' ,
701+ body : 'test content' ,
702+ } )
703+ ) . toBeRejectedWith ( jasmine . objectContaining ( {
704+ status : 400
705+ } ) ) ;
706+ // Verify no HTTP request was made to the URI
707+ expect ( requestsMade . length ) . toBe ( 0 ) ;
708+ } ) ;
709+ } ) ;
636710 } ) ;
637711
638712 describe ( 'deleting files' , ( ) => {
0 commit comments