build: Release (#9905)

This commit is contained in:
Manuel
2025-11-05 15:10:40 +01:00
committed by GitHub
12 changed files with 170 additions and 54 deletions

View File

@@ -8,7 +8,7 @@ on:
paths-ignore:
- '**/**.md'
env:
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
PARSE_SERVER_TEST_TIMEOUT: 20000
permissions:
actions: write
@@ -156,20 +156,20 @@ jobs:
- name: MongoDB 6, ReplicaSet
MONGODB_VERSION: 6.0.19
MONGODB_TOPOLOGY: replset
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
- name: MongoDB 7, ReplicaSet
MONGODB_VERSION: 7.0.16
MONGODB_TOPOLOGY: replset
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
- name: MongoDB 8, ReplicaSet
MONGODB_VERSION: 8.0.4
MONGODB_TOPOLOGY: replset
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
- name: Redis Cache
PARSE_SERVER_TEST_CACHE: redis
MONGODB_VERSION: 8.0.4
MONGODB_TOPOLOGY: standalone
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
- name: Node 20
MONGODB_VERSION: 8.0.4
MONGODB_TOPOLOGY: standalone
@@ -178,6 +178,10 @@ jobs:
MONGODB_VERSION: 8.0.4
MONGODB_TOPOLOGY: standalone
NODE_VERSION: 18.20.4
- name: Node 22
MONGODB_VERSION: 8.0.4
MONGODB_TOPOLOGY: standalone
NODE_VERSION: 22.12.0
fail-fast: false
name: ${{ matrix.name }}
timeout-minutes: 20
@@ -225,22 +229,22 @@ jobs:
include:
- name: PostgreSQL 15, PostGIS 3.3
POSTGRES_IMAGE: postgis/postgis:15-3.3
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
- name: PostgreSQL 15, PostGIS 3.4
POSTGRES_IMAGE: postgis/postgis:15-3.4
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
- name: PostgreSQL 15, PostGIS 3.5
POSTGRES_IMAGE: postgis/postgis:15-3.5
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
- name: PostgreSQL 16, PostGIS 3.5
POSTGRES_IMAGE: postgis/postgis:16-3.5
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
- name: PostgreSQL 17, PostGIS 3.5
POSTGRES_IMAGE: postgis/postgis:17-3.5
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
- name: PostgreSQL 18, PostGIS 3.6
POSTGRES_IMAGE: postgis/postgis:18-3.6
NODE_VERSION: 22.12.0
NODE_VERSION: 24.11.0
fail-fast: false
name: ${{ matrix.name }}
timeout-minutes: 20

View File

@@ -0,0 +1,44 @@
# Trigger this workflow only to manually create a docs release; this should only be used
# in extraordinary circumstances, as docs releases are normally created automatically as
# part of the automated release workflow.
name: release-manual-docs
on:
workflow_dispatch:
inputs:
ref:
default: ''
description: 'Reference (tag / SHA):'
required: true
jobs:
docs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.ref }}
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 18.20.4
- name: Cache Node.js modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Generate Docs
run: |
echo $SOURCE_TAG
npm ci
./release_docs.sh
env:
SOURCE_TAG: ${{ github.event.inputs.ref }}
- name: Deploy
uses: peaceiris/actions-gh-pages@v3.7.3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs

View File

@@ -8,7 +8,7 @@
[![Coverage](https://codecov.io/github/parse-community/parse-server/branch/alpha/graph/badge.svg)](https://app.codecov.io/github/parse-community/parse-server/tree/alpha)
[![auto-release](https://img.shields.io/badge/%F0%9F%9A%80-auto--release-9e34eb.svg)](https://github.com/parse-community/parse-dashboard/releases)
[![Node Version](https://img.shields.io/badge/nodejs-18,_20,_22-green.svg?logo=node.js&style=flat)](https://nodejs.org)
[![Node Version](https://img.shields.io/badge/nodejs-18,_20,_22,_24-green.svg?logo=node.js&style=flat)](https://nodejs.org)
[![MongoDB Version](https://img.shields.io/badge/mongodb-6,_7,_8-green.svg?logo=mongodb&style=flat)](https://www.mongodb.com)
[![Postgres Version](https://img.shields.io/badge/postgresql-13,_14,_15,_16,_17,_18-green.svg?logo=postgresql&style=flat)](https://www.postgresql.org)
@@ -130,6 +130,7 @@ Parse Server is continuously tested with the most recent releases of Node.js to
| Node.js 18 | 18.20.4 | April 2025 | <= 8.x (2025) |
| Node.js 20 | 20.18.0 | April 2026 | <= 9.x (2026) |
| Node.js 22 | 22.12.0 | April 2027 | <= 10.x (2027) |
| Node.js 24 | 24.11.0 | April 2028 | <= 11.x (2028) |
#### MongoDB

View File

@@ -1,3 +1,24 @@
# [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)
### Bug Fixes
* 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.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)
### Features
* 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))
## [8.3.1-alpha.1](https://github.com/parse-community/parse-server/compare/8.3.0...8.3.1-alpha.1) (2025-11-05)
### Bug Fixes
* 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))
# [8.3.0-alpha.14](https://github.com/parse-community/parse-server/compare/8.3.0-alpha.13...8.3.0-alpha.14) (2025-11-01)

View File

@@ -6,10 +6,10 @@
"source": {
"include": [
"README.md",
"./src/cloud-code",
"./src/Options/docs.js",
"./src/ParseServer.js",
"./src/Adapters"
"./lib/cloud-code",
"./lib/Options/docs.js",
"./lib/ParseServer.js",
"./lib/Adapters"
],
"excludePattern": "(^|\\/|\\\\)_"
},

6
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "parse-server",
"version": "8.3.0",
"version": "8.4.0-alpha.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "parse-server",
"version": "8.3.0",
"version": "8.4.0-alpha.2",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -107,7 +107,7 @@
"yaml": "2.8.0"
},
"engines": {
"node": ">=18.20.4 <19.0.0 || >=20.18.0 <21.0.0 || >=22.12.0 <23.0.0"
"node": ">=18.20.4 <19.0.0 || >=20.18.0 <21.0.0 || >=22.12.0 <23.0.0 || >=24.11.0 <25.0.0"
},
"funding": {
"type": "opencollective",

View File

@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "8.3.0",
"version": "8.4.0-alpha.2",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
@@ -142,7 +142,7 @@
},
"types": "types/index.d.ts",
"engines": {
"node": ">=18.20.4 <19.0.0 || >=20.18.0 <21.0.0 || >=22.12.0 <23.0.0"
"node": ">=18.20.4 <19.0.0 || >=20.18.0 <21.0.0 || >=22.12.0 <23.0.0 || >=24.11.0 <25.0.0"
},
"bin": {
"parse-server": "bin/parse-server"

View File

@@ -653,6 +653,80 @@ describe('Parse.File testing', () => {
done();
});
});
describe('URI-backed file upload is disabled to prevent SSRF attack', () => {
const express = require('express');
let testServer;
let testServerPort;
let requestsMade;
beforeEach(async () => {
requestsMade = [];
const app = express();
app.use((req, res) => {
requestsMade.push({ url: req.url, method: req.method });
res.status(200).send('test file content');
});
testServer = app.listen(0);
testServerPort = testServer.address().port;
});
afterEach(async () => {
if (testServer) {
await new Promise(resolve => testServer.close(resolve));
}
Parse.Cloud._removeAllHooks();
});
it('does not access URI when file upload attempted over REST', async () => {
const response = await request({
method: 'POST',
url: 'http://localhost:8378/1/classes/TestClass',
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
},
body: {
file: {
__type: 'File',
name: 'test.txt',
_source: {
format: 'uri',
uri: `http://127.0.0.1:${testServerPort}/secret-file.txt`,
},
},
},
});
expect(response.status).toBe(201);
// Verify no HTTP request was made to the URI
expect(requestsMade.length).toBe(0);
});
it('does not access URI when file created in beforeSave trigger', async () => {
Parse.Cloud.beforeSave(Parse.File, () => {
return new Parse.File('trigger-file.txt', {
uri: `http://127.0.0.1:${testServerPort}/secret-file.txt`,
});
});
await expectAsync(
request({
method: 'POST',
headers: {
'Content-Type': 'application/octet-stream',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
},
url: 'http://localhost:8378/1/files/test.txt',
body: 'test content',
})
).toBeRejectedWith(jasmine.objectContaining({
status: 400
}));
// Verify no HTTP request was made to the URI
expect(requestsMade.length).toBe(0);
});
});
});
describe('deleting files', () => {

View File

@@ -1077,9 +1077,9 @@ module.exports.FileUploadOptions = {
fileExtensions: {
env: 'PARSE_SERVER_FILE_UPLOAD_FILE_EXTENSIONS',
help:
"Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^(?!(h|H)(t|T)(m|M)(l|L)?$)` which allows any file extension except HTML files.",
"Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^(?![xXsS]?[hH][tT][mM][lL]?$)` which allows any file extension except those MIME types that are mapped to `text/html` and are rendered as website by a web browser.",
action: parsers.arrayParser,
default: ['^(?!(h|H)(t|T)(m|M)(l|L)?$)'],
default: ['^(?![xXsS]?[hH][tT][mM][lL]?$)'],
},
};
module.exports.DatabaseOptions = {

View File

@@ -235,7 +235,7 @@
* @property {Boolean} enableForAnonymousUser Is true if file upload should be allowed for anonymous users.
* @property {Boolean} enableForAuthenticatedUser Is true if file upload should be allowed for authenticated users.
* @property {Boolean} enableForPublic Is true if file upload should be allowed for anyone, regardless of user authentication.
* @property {String[]} fileExtensions Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^(?!(h|H)(t|T)(m|M)(l|L)?$)` which allows any file extension except HTML files.
* @property {String[]} fileExtensions Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^(?![xXsS]?[hH][tT][mM][lL]?$)` which allows any file extension except those MIME types that are mapped to `text/html` and are rendered as website by a web browser.
*/
/**

View File

@@ -594,8 +594,8 @@ export interface PasswordPolicyOptions {
}
export interface FileUploadOptions {
/* Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^(?!(h|H)(t|T)(m|M)(l|L)?$)` which allows any file extension except HTML files.
:DEFAULT: ["^(?!(h|H)(t|T)(m|M)(l|L)?$)"] */
/* Sets the allowed file extensions for uploading files. The extension is defined as an array of file extensions, or a regex pattern.<br><br>It is recommended to restrict the file upload extensions as much as possible. HTML files are especially problematic as they may be used by an attacker who uploads a HTML form to look legitimate under your app's domain name, or to compromise the session token of another user via accessing the browser's local storage.<br><br>Defaults to `^(?![xXsS]?[hH][tT][mM][lL]?$)` which allows any file extension except those MIME types that are mapped to `text/html` and are rendered as website by a web browser.
:DEFAULT: ["^(?![xXsS]?[hH][tT][mM][lL]?$)"] */
fileExtensions: ?(string[]);
/* Is true if file upload should be allowed for anonymous users.
:DEFAULT: false */

View File

@@ -4,34 +4,8 @@ import Parse from 'parse/node';
import Config from '../Config';
import logger from '../logger';
const triggers = require('../triggers');
const http = require('http');
const Utils = require('../Utils');
const downloadFileFromURI = uri => {
return new Promise((res, rej) => {
http
.get(uri, response => {
response.setDefaultEncoding('base64');
let body = `data:${response.headers['content-type']};base64,`;
response.on('data', data => (body += data));
response.on('end', () => res(body));
})
.on('error', e => {
rej(`Error downloading file from ${uri}: ${e.message}`);
});
});
};
const addFileDataIfNeeded = async file => {
if (file._source.format === 'uri') {
const base64 = await downloadFileFromURI(file._source.uri);
file._previousSave = file;
file._data = base64;
file._requestTask = null;
}
return file;
};
export class FilesRouter {
expressRouter({ maxUploadSize = '20Mb' } = {}) {
var router = express.Router();
@@ -247,8 +221,6 @@ export class FilesRouter {
}
// if the file returned by the trigger has already been saved skip saving anything
if (!saveResult) {
// if the ParseFile returned is type uri, download the file before saving it
await addFileDataIfNeeded(fileObject.file);
// update fileSize
const bufferData = Buffer.from(fileObject.file._data, 'base64');
fileObject.fileSize = Buffer.byteLength(bufferData);