Skip to content

Commit ade09a7

Browse files
authored
Merge branch 'alpha' into feat/upgrade-monodb-driver
2 parents 8044bc8 + f27b050 commit ade09a7

File tree

6 files changed

+96
-31
lines changed

6 files changed

+96
-31
lines changed

changelogs/CHANGELOG_alpha.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# [8.4.0-alpha.2](https://github.com/parse-community/parse-server/compare/8.4.0-alpha.1...8.4.0-alpha.2) (2025-11-05)
2+
3+
4+
### Bug Fixes
5+
6+
* Uploading a file by providing an origin URL allows for Server-Side Request Forgery (SSRF); fixes vulnerability [GHSA-x4qj-2f4q-r4rx](https://github.com/parse-community/parse-server/security/advisories/GHSA-x4qj-2f4q-r4rx) ([#9903](https://github.com/parse-community/parse-server/issues/9903)) ([9776386](https://github.com/parse-community/parse-server/commit/97763863b72689a29ad7a311dfb590c3e3c50585))
7+
18
# [8.4.0-alpha.1](https://github.com/parse-community/parse-server/compare/8.3.1-alpha.1...8.4.0-alpha.1) (2025-11-05)
29

310

changelogs/CHANGELOG_release.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# [8.4.0](https://github.com/parse-community/parse-server/compare/8.3.0...8.4.0) (2025-11-05)
2+
3+
4+
### Bug Fixes
5+
6+
* Add problematic MIME types to default value of Parse Server option `fileUpload.fileExtensions` ([#9902](https://github.com/parse-community/parse-server/issues/9902)) ([fa245cb](https://github.com/parse-community/parse-server/commit/fa245cbb5f5b7a0dad962b2ce0524fa4dafcb5f7))
7+
* Uploading a file by providing an origin URL allows for Server-Side Request Forgery (SSRF); fixes vulnerability [GHSA-x4qj-2f4q-r4rx](https://github.com/parse-community/parse-server/security/advisories/GHSA-x4qj-2f4q-r4rx) ([#9903](https://github.com/parse-community/parse-server/issues/9903)) ([9776386](https://github.com/parse-community/parse-server/commit/97763863b72689a29ad7a311dfb590c3e3c50585))
8+
9+
### Features
10+
11+
* Add support for Node 24 ([#9901](https://github.com/parse-community/parse-server/issues/9901)) ([25dfe19](https://github.com/parse-community/parse-server/commit/25dfe19fef02fd44224e4a6d198584a694a1aa52))
12+
113
# [8.3.0](https://github.com/parse-community/parse-server/compare/8.2.5...8.3.0) (2025-11-01)
214

315

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "parse-server",
3-
"version": "8.4.0-alpha.1",
3+
"version": "8.4.0",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "lib/index.js",
66
"repository": {

spec/ParseFile.spec.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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', () => {

src/Routers/FilesRouter.js

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,8 @@ import Parse from 'parse/node';
44
import Config from '../Config';
55
import logger from '../logger';
66
const triggers = require('../triggers');
7-
const http = require('http');
87
const Utils = require('../Utils');
98

10-
const downloadFileFromURI = uri => {
11-
return new Promise((res, rej) => {
12-
http
13-
.get(uri, response => {
14-
response.setDefaultEncoding('base64');
15-
let body = `data:${response.headers['content-type']};base64,`;
16-
response.on('data', data => (body += data));
17-
response.on('end', () => res(body));
18-
})
19-
.on('error', e => {
20-
rej(`Error downloading file from ${uri}: ${e.message}`);
21-
});
22-
});
23-
};
24-
25-
const addFileDataIfNeeded = async file => {
26-
if (file._source.format === 'uri') {
27-
const base64 = await downloadFileFromURI(file._source.uri);
28-
file._previousSave = file;
29-
file._data = base64;
30-
file._requestTask = null;
31-
}
32-
return file;
33-
};
34-
359
export class FilesRouter {
3610
expressRouter({ maxUploadSize = '20Mb' } = {}) {
3711
var router = express.Router();
@@ -247,8 +221,6 @@ export class FilesRouter {
247221
}
248222
// if the file returned by the trigger has already been saved skip saving anything
249223
if (!saveResult) {
250-
// if the ParseFile returned is type uri, download the file before saving it
251-
await addFileDataIfNeeded(fileObject.file);
252224
// update fileSize
253225
const bufferData = Buffer.from(fileObject.file._data, 'base64');
254226
fileObject.fileSize = Buffer.byteLength(bufferData);

0 commit comments

Comments
 (0)