fix: File creation and deletion bypasses readOnlyMasterKey write restriction (GHSA-xfh7-phr7-gr2x) (#10096)

This commit is contained in:
Manuel
2026-03-05 01:44:56 +00:00
committed by GitHub
parent d39387f44d
commit 07bddc0850
2 changed files with 70 additions and 0 deletions
+57
View File
@@ -1307,6 +1307,63 @@ describe('read-only masterKey', () => {
});
expect(Array.isArray(res.data)).toBe(true);
});
it('should throw when trying to delete a file with readOnlyMasterKey', async () => {
// Create a file with the real master key
const uploadRes = await request({
method: 'POST',
url: `${Parse.serverURL}/files/readonly-delete-test.txt`,
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': Parse.masterKey,
'Content-Type': 'text/plain',
},
body: 'file content',
});
const filename = uploadRes.data.name;
expect(filename).toBeDefined();
// Attempt delete with readOnlyMasterKey — should be rejected
loggerErrorSpy.calls.reset();
try {
await request({
method: 'DELETE',
url: `${Parse.serverURL}/files/${filename}`,
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': 'read-only-test',
},
});
fail('should have thrown');
} catch (res) {
expect(res.status).toBe(403);
expect(res.data.error).toBe('Permission denied');
}
// Verify file still exists
const getRes = await request({ url: uploadRes.data.url });
expect(getRes.status).toBe(200);
});
it('should throw when trying to create a file with readOnlyMasterKey', async () => {
loggerErrorSpy.calls.reset();
try {
await request({
method: 'POST',
url: `${Parse.serverURL}/files/readonly-create-test.txt`,
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Master-Key': 'read-only-test',
'Content-Type': 'text/plain',
},
body: 'file content',
});
fail('should have thrown');
} catch (res) {
expect(res.status).toBe(403);
expect(res.data.error).toBe('Permission denied');
}
});
});
describe('rest context', () => {
+13
View File
@@ -5,6 +5,7 @@ import Config from '../Config';
import logger from '../logger';
const triggers = require('../triggers');
const Utils = require('../Utils');
import { createSanitizedHttpError } from '../Error';
export class FilesRouter {
expressRouter({ maxUploadSize = '20Mb' } = {}) {
@@ -112,6 +113,12 @@ export class FilesRouter {
}
async createHandler(req, res, next) {
if (req.auth.isReadOnly) {
const error = createSanitizedHttpError(403, "read-only masterKey isn't allowed to create a file.", req.config);
res.status(error.status);
res.end(`{"error":"${error.message}"}`);
return;
}
const config = req.config;
const user = req.auth.user;
const isMaster = req.auth.isMaster;
@@ -266,6 +273,12 @@ export class FilesRouter {
}
async deleteHandler(req, res, next) {
if (req.auth.isReadOnly) {
const error = createSanitizedHttpError(403, "read-only masterKey isn't allowed to delete a file.", req.config);
res.status(error.status);
res.end(`{"error":"${error.message}"}`);
return;
}
try {
const { filesController } = req.config;
const { filename } = req.params;