Skip to content

Commit 26b92fa

Browse files
committed
Merge remote-tracking branch 'upstream/alpha' into alpha
2 parents 85cb005 + 87dc54c commit 26b92fa

File tree

6 files changed

+97
-6
lines changed

6 files changed

+97
-6
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+
# [9.5.0-alpha.6](https://github.com/parse-community/parse-server/compare/9.5.0-alpha.5...9.5.0-alpha.6) (2026-03-05)
2+
3+
4+
### Bug Fixes
5+
6+
* Malformed `$regex` query leaks database error details in API response ([GHSA-9cp7-3q5w-j92g](https://github.com/parse-community/parse-server/security/advisories/GHSA-9cp7-3q5w-j92g)) ([#10101](https://github.com/parse-community/parse-server/issues/10101)) ([9792d24](https://github.com/parse-community/parse-server/commit/9792d24b963f3b45e5ade2bbceb6f5c0b5d0251c))
7+
18
# [9.5.0-alpha.5](https://github.com/parse-community/parse-server/compare/9.5.0-alpha.4...9.5.0-alpha.5) (2026-03-05)
29

310

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": "9.5.0-alpha.5",
3+
"version": "9.5.0-alpha.6",
44
"description": "An express module providing a Parse-compatible API server",
55
"main": "lib/index.js",
66
"repository": {

spec/vulnerabilities.spec.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,75 @@ describe('Vulnerabilities', () => {
478478
});
479479
});
480480

481+
describe('Malformed $regex information disclosure', () => {
482+
it('should not leak database error internals for invalid regex pattern in class query', async () => {
483+
const logger = require('../lib/logger').default;
484+
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
485+
const obj = new Parse.Object('TestObject');
486+
await obj.save({ field: 'value' });
487+
488+
try {
489+
await request({
490+
method: 'GET',
491+
url: `http://localhost:8378/1/classes/TestObject`,
492+
headers: {
493+
'Content-Type': 'application/json',
494+
'X-Parse-Application-Id': 'test',
495+
'X-Parse-REST-API-Key': 'rest',
496+
},
497+
qs: {
498+
where: JSON.stringify({ field: { $regex: '[abc' } }),
499+
},
500+
});
501+
fail('Request should have failed');
502+
} catch (e) {
503+
expect(e.data.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR);
504+
expect(e.data.error).toBe('An internal server error occurred');
505+
expect(typeof e.data.error).toBe('string');
506+
expect(JSON.stringify(e.data)).not.toContain('errmsg');
507+
expect(JSON.stringify(e.data)).not.toContain('codeName');
508+
expect(JSON.stringify(e.data)).not.toContain('errorResponse');
509+
expect(loggerErrorSpy).toHaveBeenCalledWith(
510+
'Sanitized error:',
511+
jasmine.stringMatching(/[Rr]egular expression/i)
512+
);
513+
}
514+
});
515+
516+
it('should not leak database error internals for invalid regex pattern in role query', async () => {
517+
const logger = require('../lib/logger').default;
518+
const loggerErrorSpy = spyOn(logger, 'error').and.callThrough();
519+
const role = new Parse.Role('testrole', new Parse.ACL());
520+
await role.save(null, { useMasterKey: true });
521+
try {
522+
await request({
523+
method: 'GET',
524+
url: `http://localhost:8378/1/roles`,
525+
headers: {
526+
'Content-Type': 'application/json',
527+
'X-Parse-Application-Id': 'test',
528+
'X-Parse-REST-API-Key': 'rest',
529+
},
530+
qs: {
531+
where: JSON.stringify({ name: { $regex: '[abc' } }),
532+
},
533+
});
534+
fail('Request should have failed');
535+
} catch (e) {
536+
expect(e.data.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR);
537+
expect(e.data.error).toBe('An internal server error occurred');
538+
expect(typeof e.data.error).toBe('string');
539+
expect(JSON.stringify(e.data)).not.toContain('errmsg');
540+
expect(JSON.stringify(e.data)).not.toContain('codeName');
541+
expect(JSON.stringify(e.data)).not.toContain('errorResponse');
542+
expect(loggerErrorSpy).toHaveBeenCalledWith(
543+
'Sanitized error:',
544+
jasmine.stringMatching(/[Rr]egular expression/i)
545+
);
546+
}
547+
});
548+
});
549+
481550
describe('Postgres regex sanitizater', () => {
482551
it('sanitizes the regex correctly to prevent Injection', async () => {
483552
const user = new Parse.User();

src/Controllers/DatabaseController.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import SchemaCache from '../Adapters/Cache/SchemaCache';
2020
import type { LoadSchemaOptions } from './types';
2121
import type { ParseServerOptions } from '../Options';
2222
import type { QueryOptions, FullQueryOptions } from '../Adapters/Storage/StorageAdapter';
23+
import { createSanitizedError } from '../Error';
2324

2425
function addWriteACL(query, acl) {
2526
const newQuery = _.cloneDeep(query);
@@ -1366,7 +1367,19 @@ class DatabaseController {
13661367
})
13671368
)
13681369
.catch(error => {
1369-
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, error);
1370+
if (error instanceof Parse.Error) {
1371+
throw error;
1372+
}
1373+
const detailedMessage =
1374+
typeof error === 'string'
1375+
? error
1376+
: error?.message || 'An internal server error occurred';
1377+
throw createSanitizedError(
1378+
Parse.Error.INTERNAL_SERVER_ERROR,
1379+
detailedMessage,
1380+
this.options,
1381+
'An internal server error occurred'
1382+
);
13701383
});
13711384
}
13721385
});

src/Error.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ import defaultLogger from './logger';
66
*
77
* @param {number} errorCode - The Parse.Error code (e.g., Parse.Error.OPERATION_FORBIDDEN)
88
* @param {string} detailedMessage - The detailed error message to log server-side
9+
* @param {object} config - Parse Server config with enableSanitizedErrorResponse
10+
* @param {string} [sanitizedMessage='Permission denied'] - The sanitized message to return to clients
911
* @returns {Parse.Error} A Parse.Error with sanitized message
1012
*/
11-
function createSanitizedError(errorCode, detailedMessage, config) {
13+
function createSanitizedError(errorCode, detailedMessage, config, sanitizedMessage = 'Permission denied') {
1214
// On testing we need to add a prefix to the message to allow to find the correct call in the TestUtils.js file
1315
if (process.env.TESTING) {
1416
defaultLogger.error('Sanitized error:', detailedMessage);
1517
} else {
1618
defaultLogger.error(detailedMessage);
1719
}
1820

19-
return new Parse.Error(errorCode, config?.enableSanitizedErrorResponse !== false ? 'Permission denied' : detailedMessage);
21+
return new Parse.Error(errorCode, config?.enableSanitizedErrorResponse !== false ? sanitizedMessage : detailedMessage);
2022
}
2123

2224
/**

0 commit comments

Comments
 (0)