Compare commits

..
8 Commits
Author SHA1 Message Date
semantic-release-bot e397b83c51 chore(release): 8.6.57 [skip ci]
## [8.6.57](https://github.com/parse-community/parse-server/compare/8.6.56...8.6.57) (2026-03-21)

### Bug Fixes

* Session update endpoint allows overwriting server-generated session fields ([GHSA-jc39-686j-wp6q](https://github.com/parse-community/parse-server/security/advisories/GHSA-jc39-686j-wp6q)) ([#10264](https://github.com/parse-community/parse-server/issues/10264)) ([26b628c](https://github.com/parse-community/parse-server/commit/26b628c8fb3cc79ea955374769eebcff6f8a8a73))
2026-03-21 01:38:47 +00:00
Manuel 26b628c8fb fix: Session update endpoint allows overwriting server-generated session fields ([GHSA-jc39-686j-wp6q](https://github.com/parse-community/parse-server/security/advisories/GHSA-jc39-686j-wp6q)) (#10264) 2026-03-21 01:37:55 +00:00
semantic-release-bot a59a60794e chore(release): 8.6.56 [skip ci]
## [8.6.56](https://github.com/parse-community/parse-server/compare/8.6.55...8.6.56) (2026-03-20)

### Bug Fixes

* LiveQuery subscription query depth bypass ([GHSA-6qh5-m6g3-xhq6](https://github.com/parse-community/parse-server/security/advisories/GHSA-6qh5-m6g3-xhq6)) ([#10260](https://github.com/parse-community/parse-server/issues/10260)) ([060d270](https://github.com/parse-community/parse-server/commit/060d27053fb0fadf613c25aabab7fe0c82b7a899))
2026-03-20 19:22:41 +00:00
Manuel 060d27053f fix: LiveQuery subscription query depth bypass ([GHSA-6qh5-m6g3-xhq6](https://github.com/parse-community/parse-server/security/advisories/GHSA-6qh5-m6g3-xhq6)) (#10260) 2026-03-20 19:21:39 +00:00
semantic-release-bot ec028e7354 chore(release): 8.6.55 [skip ci]
## [8.6.55](https://github.com/parse-community/parse-server/compare/8.6.54...8.6.55) (2026-03-20)

### Bug Fixes

* Query condition depth bypass via pre-validation transform pipeline ([GHSA-9fjp-q3c4-6w3j](https://github.com/parse-community/parse-server/security/advisories/GHSA-9fjp-q3c4-6w3j)) ([#10258](https://github.com/parse-community/parse-server/issues/10258)) ([2581b54](https://github.com/parse-community/parse-server/commit/2581b5426047ce9cbcd3d9c0e8379e9c30e23ab5))
2026-03-20 17:44:41 +00:00
Manuel 2581b54260 fix: Query condition depth bypass via pre-validation transform pipeline ([GHSA-9fjp-q3c4-6w3j](https://github.com/parse-community/parse-server/security/advisories/GHSA-9fjp-q3c4-6w3j)) (#10258) 2026-03-20 17:43:37 +00:00
semantic-release-bot 9cda64fdc0 chore(release): 8.6.54 [skip ci]
## [8.6.54](https://github.com/parse-community/parse-server/compare/8.6.53...8.6.54) (2026-03-20)

### Bug Fixes

* Protected field change detection oracle via LiveQuery watch parameter ([GHSA-qpc3-fg4j-8hgm](https://github.com/parse-community/parse-server/security/advisories/GHSA-qpc3-fg4j-8hgm)) ([#10254](https://github.com/parse-community/parse-server/issues/10254)) ([c62eaca](https://github.com/parse-community/parse-server/commit/c62eacaf38de86913f09240583448360b1cc8e67))
2026-03-20 04:06:15 +00:00
Manuel c62eacaf38 fix: Protected field change detection oracle via LiveQuery watch parameter ([GHSA-qpc3-fg4j-8hgm](https://github.com/parse-community/parse-server/security/advisories/GHSA-qpc3-fg4j-8hgm)) (#10254) 2026-03-20 04:05:14 +00:00
8 changed files with 545 additions and 5 deletions
+28
View File
@@ -1,3 +1,31 @@
## [8.6.57](https://github.com/parse-community/parse-server/compare/8.6.56...8.6.57) (2026-03-21)
### Bug Fixes
* Session update endpoint allows overwriting server-generated session fields ([GHSA-jc39-686j-wp6q](https://github.com/parse-community/parse-server/security/advisories/GHSA-jc39-686j-wp6q)) ([#10264](https://github.com/parse-community/parse-server/issues/10264)) ([26b628c](https://github.com/parse-community/parse-server/commit/26b628c8fb3cc79ea955374769eebcff6f8a8a73))
## [8.6.56](https://github.com/parse-community/parse-server/compare/8.6.55...8.6.56) (2026-03-20)
### Bug Fixes
* LiveQuery subscription query depth bypass ([GHSA-6qh5-m6g3-xhq6](https://github.com/parse-community/parse-server/security/advisories/GHSA-6qh5-m6g3-xhq6)) ([#10260](https://github.com/parse-community/parse-server/issues/10260)) ([060d270](https://github.com/parse-community/parse-server/commit/060d27053fb0fadf613c25aabab7fe0c82b7a899))
## [8.6.55](https://github.com/parse-community/parse-server/compare/8.6.54...8.6.55) (2026-03-20)
### Bug Fixes
* Query condition depth bypass via pre-validation transform pipeline ([GHSA-9fjp-q3c4-6w3j](https://github.com/parse-community/parse-server/security/advisories/GHSA-9fjp-q3c4-6w3j)) ([#10258](https://github.com/parse-community/parse-server/issues/10258)) ([2581b54](https://github.com/parse-community/parse-server/commit/2581b5426047ce9cbcd3d9c0e8379e9c30e23ab5))
## [8.6.54](https://github.com/parse-community/parse-server/compare/8.6.53...8.6.54) (2026-03-20)
### Bug Fixes
* Protected field change detection oracle via LiveQuery watch parameter ([GHSA-qpc3-fg4j-8hgm](https://github.com/parse-community/parse-server/security/advisories/GHSA-qpc3-fg4j-8hgm)) ([#10254](https://github.com/parse-community/parse-server/issues/10254)) ([c62eaca](https://github.com/parse-community/parse-server/commit/c62eacaf38de86913f09240583448360b1cc8e67))
## [8.6.53](https://github.com/parse-community/parse-server/compare/8.6.52...8.6.53) (2026-03-20)
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "parse-server",
"version": "8.6.53",
"version": "8.6.57",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "parse-server",
"version": "8.6.53",
"version": "8.6.57",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "8.6.53",
"version": "8.6.57",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
+137
View File
@@ -256,4 +256,141 @@ describe('Parse.Session', () => {
expect(newSession.createdWith.action).toBe('create');
expect(newSession.createdWith.authProvider).toBeUndefined();
});
it('should reject expiresAt when updating a session via PUT', async () => {
const user = await Parse.User.signUp('sessionupdateuser1', 'password');
const sessionToken = user.getSessionToken();
// Get the session objectId
const sessionRes = await request({
method: 'GET',
url: 'http://localhost:8378/1/sessions/me',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': sessionToken,
},
});
const sessionId = sessionRes.data.objectId;
const originalExpiresAt = sessionRes.data.expiresAt;
// Attempt to overwrite expiresAt via PUT
const updateRes = await request({
method: 'PUT',
url: `http://localhost:8378/1/sessions/${sessionId}`,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': sessionToken,
'Content-Type': 'application/json',
},
body: {
expiresAt: { __type: 'Date', iso: '2099-12-31T23:59:59.000Z' },
},
}).catch(e => e);
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
// Verify expiresAt was not changed
const verifyRes = await request({
method: 'GET',
url: 'http://localhost:8378/1/sessions/me',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': sessionToken,
},
});
expect(verifyRes.data.expiresAt).toEqual(originalExpiresAt);
});
it('should reject createdWith when updating a session via PUT', async () => {
const user = await Parse.User.signUp('sessionupdateuser2', 'password');
const sessionToken = user.getSessionToken();
// Get the session objectId
const sessionRes = await request({
method: 'GET',
url: 'http://localhost:8378/1/sessions/me',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': sessionToken,
},
});
const sessionId = sessionRes.data.objectId;
const originalCreatedWith = sessionRes.data.createdWith;
// Attempt to overwrite createdWith via PUT
const updateRes = await request({
method: 'PUT',
url: `http://localhost:8378/1/sessions/${sessionId}`,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': sessionToken,
'Content-Type': 'application/json',
},
body: {
createdWith: { action: 'attacker', authProvider: 'evil' },
},
}).catch(e => e);
expect(updateRes.data.code).toBe(Parse.Error.INVALID_KEY_NAME);
// Verify createdWith was not changed
const verifyRes = await request({
method: 'GET',
url: 'http://localhost:8378/1/sessions/me',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': sessionToken,
},
});
expect(verifyRes.data.createdWith).toEqual(originalCreatedWith);
});
it('should allow master key to update expiresAt on a session', async () => {
const user = await Parse.User.signUp('sessionupdateuser3', 'password');
const sessionToken = user.getSessionToken();
// Get the session objectId
const sessionRes = await request({
method: 'GET',
url: 'http://localhost:8378/1/sessions/me',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Session-Token': sessionToken,
},
});
const sessionId = sessionRes.data.objectId;
const farFuture = '2099-12-31T23:59:59.000Z';
// Master key should be able to update expiresAt
await request({
method: 'PUT',
url: `http://localhost:8378/1/sessions/${sessionId}`,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
'Content-Type': 'application/json',
},
body: {
expiresAt: { __type: 'Date', iso: farFuture },
},
});
// Verify expiresAt was changed
const verifyRes = await request({
method: 'GET',
url: `http://localhost:8378/1/sessions/${sessionId}`,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
});
expect(verifyRes.data.expiresAt.iso).toBe(farFuture);
});
});
+300
View File
@@ -2700,6 +2700,90 @@ describe('(GHSA-9xp9-j92r-p88v) Stack overflow process crash via deeply nested q
);
});
it('rejects deeply nested query before transform pipeline processes it', async () => {
await reconfigureServer({
requestComplexity: { queryDepth: 10 },
});
const auth = require('../lib/Auth');
const rest = require('../lib/rest');
const config = Config.get('test');
// Depth 50 bypasses the fix because RestQuery.js transform pipeline
// recursively traverses the structure before validateQuery() is reached
let where = { username: 'test' };
for (let i = 0; i < 50; i++) {
where = { $and: [where] };
}
await expectAsync(
rest.find(config, auth.nobody(config), '_User', where)
).toBeRejectedWith(
jasmine.objectContaining({
message: jasmine.stringMatching(/Query condition nesting depth exceeds maximum allowed depth/),
})
);
});
it('rejects deeply nested query via REST API without authentication', async () => {
await reconfigureServer({
requestComplexity: { queryDepth: 10 },
});
let where = { username: 'test' };
for (let i = 0; i < 50; i++) {
where = { $or: [where] };
}
await expectAsync(
request({
method: 'GET',
url: `${Parse.serverURL}/classes/_User`,
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
qs: { where: JSON.stringify(where) },
})
).toBeRejectedWith(
jasmine.objectContaining({
data: jasmine.objectContaining({
code: Parse.Error.INVALID_QUERY,
}),
})
);
});
it('rejects deeply nested $nor query before transform pipeline', async () => {
await reconfigureServer({
requestComplexity: { queryDepth: 10 },
});
const auth = require('../lib/Auth');
const rest = require('../lib/rest');
const config = Config.get('test');
let where = { username: 'test' };
for (let i = 0; i < 50; i++) {
where = { $nor: [where] };
}
await expectAsync(
rest.find(config, auth.nobody(config), '_User', where)
).toBeRejectedWith(
jasmine.objectContaining({
message: jasmine.stringMatching(/Query condition nesting depth exceeds maximum allowed depth/),
})
);
});
it('allows queries within the depth limit', async () => {
await reconfigureServer({
requestComplexity: { queryDepth: 10 },
});
const auth = require('../lib/Auth');
const rest = require('../lib/rest');
const config = Config.get('test');
let where = { username: 'test' };
for (let i = 0; i < 5; i++) {
where = { $or: [where] };
}
const result = await rest.find(config, auth.nobody(config), '_User', where);
expect(result.results).toBeDefined();
});
describe('(GHSA-wjqw-r9x4-j59v) Empty authData session issuance bypass', () => {
const signupHeaders = {
'Content-Type': 'application/json',
@@ -3416,3 +3500,219 @@ describe('(GHSA-fph2-r4qg-9576) LiveQuery bypasses CLP pointer permission enforc
expect(createSpyC).not.toHaveBeenCalled();
});
});
describe('(GHSA-qpc3-fg4j-8hgm) Protected field change detection oracle via LiveQuery watch parameter', () => {
const { sleep } = require('../lib/TestUtils');
let obj;
beforeEach(async () => {
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(null);
await reconfigureServer({
liveQuery: { classNames: ['SecretClass'] },
startLiveQueryServer: true,
verbose: false,
silent: true,
});
const config = Config.get(Parse.applicationId);
const schemaController = await config.database.loadSchema();
await schemaController.addClassIfNotExists('SecretClass', {
secretObj: { type: 'Object' },
publicField: { type: 'String' },
});
await schemaController.updateClass(
'SecretClass',
{},
{
find: { '*': true },
get: { '*': true },
create: { '*': true },
update: { '*': true },
delete: { '*': true },
addField: {},
protectedFields: { '*': ['secretObj'] },
}
);
obj = new Parse.Object('SecretClass');
obj.set('secretObj', { apiKey: 'SENSITIVE_KEY_123', score: 42 });
obj.set('publicField', 'visible');
await obj.save(null, { useMasterKey: true });
});
afterEach(async () => {
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
if (client) {
await client.close();
}
});
it('should reject LiveQuery subscription with protected field in watch', async () => {
const query = new Parse.Query('SecretClass');
query.watch('secretObj');
await expectAsync(query.subscribe()).toBeRejectedWith(
new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Permission denied')
);
});
it('should reject LiveQuery subscription with dot-notation on protected field in watch', async () => {
const query = new Parse.Query('SecretClass');
query.watch('secretObj.apiKey');
await expectAsync(query.subscribe()).toBeRejectedWith(
new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Permission denied')
);
});
it('should reject LiveQuery subscription with deeply nested dot-notation on protected field in watch', async () => {
const query = new Parse.Query('SecretClass');
query.watch('secretObj.nested.deep.key');
await expectAsync(query.subscribe()).toBeRejectedWith(
new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Permission denied')
);
});
it('should allow LiveQuery subscription with non-protected field in watch', async () => {
const query = new Parse.Query('SecretClass');
query.watch('publicField');
const subscription = await query.subscribe();
await Promise.all([
new Promise(resolve => {
subscription.on('update', object => {
expect(object.get('secretObj')).toBeUndefined();
expect(object.get('publicField')).toBe('updated');
resolve();
});
}),
obj.save({ publicField: 'updated' }, { useMasterKey: true }),
]);
});
it('should not deliver update event when only non-watched field changes', async () => {
const query = new Parse.Query('SecretClass');
query.watch('publicField');
const subscription = await query.subscribe();
const updateSpy = jasmine.createSpy('update');
subscription.on('update', updateSpy);
// Change a field that is NOT in the watch list
obj.set('secretObj', { apiKey: 'ROTATED_KEY', score: 99 });
await obj.save(null, { useMasterKey: true });
await sleep(500);
expect(updateSpy).not.toHaveBeenCalled();
});
});
describe('(GHSA-6qh5-m6g3-xhq6) LiveQuery query depth DoS via deeply nested subscription', () => {
afterEach(async () => {
const client = await Parse.CoreManager.getLiveQueryController().getDefaultLiveQueryClient();
if (client) {
await client.close();
}
});
it('should reject LiveQuery subscription with deeply nested $or when queryDepth is set', async () => {
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(null);
await reconfigureServer({
liveQuery: { classNames: ['TestClass'] },
startLiveQueryServer: true,
verbose: false,
silent: true,
requestComplexity: { queryDepth: 10 },
});
const query = new Parse.Query('TestClass');
let where = { field: 'value' };
for (let i = 0; i < 15; i++) {
where = { $or: [where] };
}
query._where = where;
await expectAsync(query.subscribe()).toBeRejectedWith(
jasmine.objectContaining({
code: Parse.Error.INVALID_QUERY,
message: jasmine.stringMatching(/Query condition nesting depth exceeds maximum allowed depth/),
})
);
});
it('should reject LiveQuery subscription with deeply nested $and when queryDepth is set', async () => {
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(null);
await reconfigureServer({
liveQuery: { classNames: ['TestClass'] },
startLiveQueryServer: true,
verbose: false,
silent: true,
requestComplexity: { queryDepth: 10 },
});
const query = new Parse.Query('TestClass');
let where = { field: 'value' };
for (let i = 0; i < 50; i++) {
where = { $and: [where] };
}
query._where = where;
await expectAsync(query.subscribe()).toBeRejectedWith(
jasmine.objectContaining({
code: Parse.Error.INVALID_QUERY,
message: jasmine.stringMatching(/Query condition nesting depth exceeds maximum allowed depth/),
})
);
});
it('should reject LiveQuery subscription with deeply nested $nor when queryDepth is set', async () => {
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(null);
await reconfigureServer({
liveQuery: { classNames: ['TestClass'] },
startLiveQueryServer: true,
verbose: false,
silent: true,
requestComplexity: { queryDepth: 10 },
});
const query = new Parse.Query('TestClass');
let where = { field: 'value' };
for (let i = 0; i < 50; i++) {
where = { $nor: [where] };
}
query._where = where;
await expectAsync(query.subscribe()).toBeRejectedWith(
jasmine.objectContaining({
code: Parse.Error.INVALID_QUERY,
message: jasmine.stringMatching(/Query condition nesting depth exceeds maximum allowed depth/),
})
);
});
it('should allow LiveQuery subscription within the depth limit', async () => {
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(null);
await reconfigureServer({
liveQuery: { classNames: ['TestClass'] },
startLiveQueryServer: true,
verbose: false,
silent: true,
requestComplexity: { queryDepth: 10 },
});
const query = new Parse.Query('TestClass');
let where = { field: 'value' };
for (let i = 0; i < 5; i++) {
where = { $or: [where] };
}
query._where = where;
const subscription = await query.subscribe();
expect(subscription).toBeDefined();
});
it('should allow LiveQuery subscription when queryDepth is disabled', async () => {
Parse.CoreManager.getLiveQueryController().setDefaultLiveQueryClient(null);
await reconfigureServer({
liveQuery: { classNames: ['TestClass'] },
startLiveQueryServer: true,
verbose: false,
silent: true,
requestComplexity: { queryDepth: -1 },
});
const query = new Parse.Query('TestClass');
let where = { field: 'value' };
for (let i = 0; i < 15; i++) {
where = { $or: [where] };
}
query._where = where;
const subscription = await query.subscribe();
expect(subscription).toBeDefined();
});
});
+40 -2
View File
@@ -1023,8 +1023,35 @@ class ParseLiveQueryServer {
return;
}
}
// Check CLP for subscribe operation
// Validate query condition depth
const appConfig = Config.get(this.config.appId);
if (!client.hasMasterKey) {
const rc = appConfig.requestComplexity;
if (rc && rc.queryDepth !== -1) {
const maxDepth = rc.queryDepth;
const checkDepth = (where: any, depth: number) => {
if (depth > maxDepth) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
`Query condition nesting depth exceeds maximum allowed depth of ${maxDepth}`
);
}
if (typeof where !== 'object' || where === null) {
return;
}
for (const op of ['$or', '$and', '$nor']) {
if (Array.isArray(where[op])) {
for (const subQuery of where[op]) {
checkDepth(subQuery, depth + 1);
}
}
}
};
checkDepth(request.query.where, 0);
}
}
// Check CLP for subscribe operation
const schemaController = await appConfig.database.loadSchema();
const classLevelPermissions = schemaController.getClassLevelPermissions(className);
const op = this._getCLPOperation(request.query);
@@ -1050,7 +1077,7 @@ class ParseLiveQueryServer {
op
);
// Check protected fields in WHERE clause
// Check protected fields in WHERE clause and WATCH parameter
if (!client.hasMasterKey) {
const auth = request.user ? { user: request.user, userRoles: [] } : {};
const protectedFields =
@@ -1083,6 +1110,17 @@ class ParseLiveQueryServer {
};
checkWhere(request.query.where);
}
if (protectedFields.length > 0 && Array.isArray(request.query.watch)) {
for (const watchField of request.query.watch) {
const rootField = watchField.split('.')[0];
if (protectedFields.includes(watchField) || protectedFields.includes(rootField)) {
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
'Permission denied'
);
}
}
}
}
// Validate regex patterns in the subscription query
+33
View File
@@ -281,6 +281,9 @@ function _UnsafeRestQuery(
// TODO: consolidate the replaceX functions
_UnsafeRestQuery.prototype.execute = function (executeOptions) {
return Promise.resolve()
.then(() => {
return this.validateQueryDepth();
})
.then(() => {
return this.buildRestWhere();
})
@@ -352,6 +355,36 @@ _UnsafeRestQuery.prototype.each = function (callback) {
);
};
_UnsafeRestQuery.prototype.validateQueryDepth = function () {
if (this.auth.isMaster || this.auth.isMaintenance) {
return;
}
const rc = this.config.requestComplexity;
if (!rc || rc.queryDepth === -1) {
return;
}
const maxDepth = rc.queryDepth;
const checkDepth = (where, depth) => {
if (depth > maxDepth) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
`Query condition nesting depth exceeds maximum allowed depth of ${maxDepth}`
);
}
if (typeof where !== 'object' || where === null) {
return;
}
for (const op of ['$or', '$and', '$nor']) {
if (Array.isArray(where[op])) {
for (const subQuery of where[op]) {
checkDepth(subQuery, depth + 1);
}
}
}
};
checkDepth(this.restWhere, 0);
};
_UnsafeRestQuery.prototype.buildRestWhere = function () {
return Promise.resolve()
.then(() => {
+4
View File
@@ -1147,6 +1147,10 @@ RestWrite.prototype.handleSession = function () {
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
} else if (this.data.sessionToken) {
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
} else if (this.data.expiresAt && !this.auth.isMaster && !this.auth.isMaintenance) {
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
} else if (this.data.createdWith && !this.auth.isMaster && !this.auth.isMaintenance) {
throw new Parse.Error(Parse.Error.INVALID_KEY_NAME);
}
if (!this.auth.isMaster) {
this.query = {