build: Release (#9905)
This commit is contained in:
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
44
.github/workflows/release-manual-docs.yml
vendored
Normal file
44
.github/workflows/release-manual-docs.yml
vendored
Normal 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
|
||||
@@ -8,7 +8,7 @@
|
||||
[](https://app.codecov.io/github/parse-community/parse-server/tree/alpha)
|
||||
[](https://github.com/parse-community/parse-dashboard/releases)
|
||||
|
||||
[](https://nodejs.org)
|
||||
[](https://nodejs.org)
|
||||
[](https://www.mongodb.com)
|
||||
[](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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
6
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user