Moderate severityNVD Advisory· Published Jul 22, 2020· Updated Aug 4, 2024
Information disclosure through Viewer query in parse-server
CVE-2020-15126
Description
In parser-server from version 3.5.0 and before 4.3.0, an authenticated user using the viewer GraphQL query can by pass all read security on his User object and can also by pass all objects linked via relation or Pointer on his User object.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
parse-servernpm | >= 3.5.0, < 4.3.0 | 4.3.0 |
Affected products
1- Range: >= 3.5.0, < 4.3.0
Patches
178239ac90711Merge pull request from GHSA-236h-rqv8-8q73
3 files changed · +226 −181
spec/ParseGraphQLServer.spec.js+187 −143 modified@@ -170,7 +170,7 @@ describe('ParseGraphQLServer', () => { new ParseGraphQLServer(parseServer, { graphQLPath: 'somepath', }).applyGraphQL({ - use: (path) => { + use: path => { useCount++; expect(path).toEqual('somepath'); }, @@ -208,7 +208,7 @@ describe('ParseGraphQLServer', () => { graphQLPath: 'graphQL', playgroundPath: 'somepath', }).applyPlayground({ - get: (path) => { + get: path => { useCount++; expect(path).toEqual('somepath'); }, @@ -436,9 +436,7 @@ describe('ParseGraphQLServer', () => { parseGraphQLServer.applyGraphQL(expressApp); parseGraphQLServer.applyPlayground(expressApp); parseGraphQLServer.createSubscriptions(httpServer); - await new Promise((resolve) => - httpServer.listen({ port: 13377 }, resolve) - ); + await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve)); const subscriptionClient = new SubscriptionClient( 'ws://localhost:13377/subscriptions', @@ -506,7 +504,7 @@ describe('ParseGraphQLServer', () => { let checked = false; const apolloClient = new ApolloClient({ link: new ApolloLink((operation, forward) => { - return forward(operation).map((response) => { + return forward(operation).map(response => { const context = operation.getContext(); const { response: { headers }, @@ -541,7 +539,7 @@ describe('ParseGraphQLServer', () => { it('should handle Parse headers', async () => { let checked = false; const originalGetGraphQLOptions = parseGraphQLServer._getGraphQLOptions; - parseGraphQLServer._getGraphQLOptions = async (req) => { + parseGraphQLServer._getGraphQLOptions = async req => { expect(req.info).toBeDefined(); expect(req.config).toBeDefined(); expect(req.auth).toBeDefined(); @@ -643,7 +641,7 @@ describe('ParseGraphQLServer', () => { }) ).data['__type']; expect(fileType.kind).toEqual('OBJECT'); - expect(fileType.fields.map((field) => field.name).sort()).toEqual([ + expect(fileType.fields.map(field => field.name).sort()).toEqual([ 'name', 'url', ]); @@ -665,7 +663,7 @@ describe('ParseGraphQLServer', () => { }) ).data['__type']; expect(classType.kind).toEqual('INTERFACE'); - expect(classType.fields.map((field) => field.name).sort()).toEqual([ + expect(classType.fields.map(field => field.name).sort()).toEqual([ 'ACL', 'createdAt', 'objectId', @@ -690,7 +688,7 @@ describe('ParseGraphQLServer', () => { ).data['__type']; expect(readPreferenceType.kind).toEqual('ENUM'); expect( - readPreferenceType.enumValues.map((value) => value.name).sort() + readPreferenceType.enumValues.map(value => value.name).sort() ).toEqual([ 'NEAREST', 'PRIMARY', @@ -731,7 +729,7 @@ describe('ParseGraphQLServer', () => { } `, }) - ).data['__schema'].types.map((type) => type.name); + ).data['__schema'].types.map(type => type.name); const expectedTypes = [ 'ParseObject', @@ -741,7 +739,7 @@ describe('ParseGraphQLServer', () => { 'Upload', ]; expect( - expectedTypes.every((type) => schemaTypes.indexOf(type) !== -1) + expectedTypes.every(type => schemaTypes.indexOf(type) !== -1) ).toBeTruthy(JSON.stringify(schemaTypes.types)); }); }); @@ -768,7 +766,7 @@ describe('ParseGraphQLServer', () => { } `, }) - ).data['__schema'].types.map((type) => type.name); + ).data['__schema'].types.map(type => type.name); expect(schemaTypes).toContain('Node'); }); @@ -786,7 +784,7 @@ describe('ParseGraphQLServer', () => { } `, }) - ).data['__type'].fields.map((field) => field.name); + ).data['__type'].fields.map(field => field.name); expect(queryFields).toContain('node'); }); @@ -804,7 +802,7 @@ describe('ParseGraphQLServer', () => { } `, }) - ).data['__type'].fields.map((field) => field.name); + ).data['__type'].fields.map(field => field.name); expect(userFields).toContain('id'); expect(userFields).toContain('objectId'); @@ -824,7 +822,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(createFileInputFields).toEqual(['clientMutationId', 'upload']); @@ -844,7 +842,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(createFilePayloadFields).toEqual([ @@ -869,7 +867,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(callFunctionInputFields).toEqual([ @@ -895,7 +893,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(callFunctionPayloadFields).toEqual([ @@ -918,7 +916,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(inputFields).toEqual(['clientMutationId', 'fields']); @@ -938,7 +936,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(payloadFields).toEqual(['clientMutationId', 'viewer']); @@ -958,7 +956,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(inputFields).toEqual([ @@ -982,7 +980,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(payloadFields).toEqual(['clientMutationId', 'viewer']); @@ -1002,7 +1000,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(inputFields).toEqual(['clientMutationId']); @@ -1022,7 +1020,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(payloadFields).toEqual(['clientMutationId', 'viewer']); @@ -1042,7 +1040,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(inputFields).toEqual([ @@ -1066,7 +1064,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(payloadFields).toEqual(['class', 'clientMutationId']); @@ -1086,7 +1084,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(inputFields).toEqual([ @@ -1110,7 +1108,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(payloadFields).toEqual(['class', 'clientMutationId']); @@ -1130,7 +1128,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(inputFields).toEqual(['clientMutationId', 'name']); @@ -1150,7 +1148,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(payloadFields).toEqual(['class', 'clientMutationId']); @@ -1175,7 +1173,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(createObjectInputFields).toEqual([ @@ -1203,7 +1201,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(createObjectPayloadFields).toEqual([ @@ -1231,7 +1229,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(createObjectInputFields).toEqual([ @@ -1260,7 +1258,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(createObjectPayloadFields).toEqual([ @@ -1288,7 +1286,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].inputFields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(createObjectInputFields).toEqual(['clientMutationId', 'id']); @@ -1313,7 +1311,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type'].fields - .map((field) => field.name) + .map(field => field.name) .sort(); expect(createObjectPayloadFields).toEqual([ @@ -1339,7 +1337,7 @@ describe('ParseGraphQLServer', () => { } `, }) - ).data['__schema'].types.map((type) => type.name); + ).data['__schema'].types.map(type => type.name); const expectedTypes = [ 'Role', @@ -1354,7 +1352,7 @@ describe('ParseGraphQLServer', () => { 'UpdateUserFieldsInput', ]; expect( - expectedTypes.every((type) => schemaTypes.indexOf(type) !== -1) + expectedTypes.every(type => schemaTypes.indexOf(type) !== -1) ).toBeTruthy(JSON.stringify(schemaTypes)); }); @@ -1373,7 +1371,7 @@ describe('ParseGraphQLServer', () => { `, }) ).data['__type']; - const possibleTypes = objectType.possibleTypes.map((o) => o.name); + const possibleTypes = objectType.possibleTypes.map(o => o.name); expect(possibleTypes).toContain('User'); expect(possibleTypes).toContain('Role'); expect(possibleTypes).toContain('Element'); @@ -1397,7 +1395,7 @@ describe('ParseGraphQLServer', () => { } `, }) - ).data['__type'].fields.map((field) => field.name); + ).data['__type'].fields.map(field => field.name); expect(userFields.indexOf('foo') !== -1).toBeTruthy(); }); @@ -1414,7 +1412,7 @@ describe('ParseGraphQLServer', () => { } `, }) - ).data['__type'].fields.map((field) => field.name); + ).data['__type'].fields.map(field => field.name); expect(userFields.includes('password')).toBeFalsy(); }); }); @@ -1896,13 +1894,13 @@ describe('ParseGraphQLServer', () => { `, }); expect( - __type.inputFields.find((o) => o.name === 'price').type.kind + __type.inputFields.find(o => o.name === 'price').type.kind ).toEqual('SCALAR'); expect( - __type.inputFields.find((o) => o.name === 'engine').type.kind + __type.inputFields.find(o => o.name === 'engine').type.kind ).toEqual('NON_NULL'); expect( - __type.inputFields.find((o) => o.name === 'doors').type.kind + __type.inputFields.find(o => o.name === 'doors').type.kind ).toEqual('NON_NULL'); const { @@ -1922,13 +1920,13 @@ describe('ParseGraphQLServer', () => { `, }); expect( - __type2.fields.find((o) => o.name === 'price').type.kind + __type2.fields.find(o => o.name === 'price').type.kind ).toEqual('SCALAR'); expect( - __type2.fields.find((o) => o.name === 'engine').type.kind + __type2.fields.find(o => o.name === 'engine').type.kind ).toEqual('NON_NULL'); expect( - __type2.fields.find((o) => o.name === 'doors').type.kind + __type2.fields.find(o => o.name === 'doors').type.kind ).toEqual('NON_NULL'); }); @@ -2787,7 +2785,7 @@ describe('ParseGraphQLServer', () => { ).toEqual(2); expect( findSecondaryObjectsResult.data.secondaryObjects.edges - .map((value) => value.node.someField) + .map(value => value.node.someField) .sort() ).toEqual(['some value 22', 'some value 44']); expect( @@ -2954,7 +2952,7 @@ describe('ParseGraphQLServer', () => { ).toEqual('some value 22'); expect( createPrimaryObjectResult.data.createPrimaryObject.primaryObject.relationField.edges - .map((value) => value.node.someField) + .map(value => value.node.someField) .sort() ).toEqual(['some value 22', 'some value 44']); expect( @@ -3193,7 +3191,7 @@ describe('ParseGraphQLServer', () => { }, }, }); - const classes = Object.keys(result.data).map((fieldName) => ({ + const classes = Object.keys(result.data).map(fieldName => ({ clientMutationId: result.data[fieldName].clientMutationId, class: { name: result.data[fieldName].class.name, @@ -3358,9 +3356,9 @@ describe('ParseGraphQLServer', () => { }, }); findResult.data.classes = findResult.data.classes - .filter((schemaClass) => !schemaClass.name.startsWith('_')) + .filter(schemaClass => !schemaClass.name.startsWith('_')) .sort((a, b) => (a.name > b.name ? 1 : -1)); - findResult.data.classes.forEach((schemaClass) => { + findResult.data.classes.forEach(schemaClass => { schemaClass.schemaFields = schemaClass.schemaFields.sort((a, b) => a.name > b.name ? 1 : -1 ); @@ -4277,10 +4275,10 @@ describe('ParseGraphQLServer', () => { expect(result.manyRelations.length).toEqual(2); const customerSubObject = result.manyRelations.find( - (o) => o.objectId === obj1.id + o => o.objectId === obj1.id ); const someClassSubObject = result.manyRelations.find( - (o) => o.objectId === obj2.id + o => o.objectId === obj2.id ); expect(customerSubObject).toBeDefined(); @@ -4289,7 +4287,7 @@ describe('ParseGraphQLServer', () => { 'imCustomerOne' ); const formatedArrayField = customerSubObject.arrayField.map( - (elem) => elem.value + elem => elem.value ); expect(formatedArrayField).toEqual(arrayField); expect(someClassSubObject.someClassField).toEqual( @@ -4445,7 +4443,7 @@ describe('ParseGraphQLServer', () => { await Promise.all( objects .slice(0, 3) - .map((obj) => + .map(obj => expectAsync( getObject(obj.className, obj.id) ).toBeRejectedWith(jasmine.stringMatching('Object not found')) @@ -4456,7 +4454,7 @@ describe('ParseGraphQLServer', () => { .someField ).toEqual('someValue4'); await Promise.all( - objects.map(async (obj) => + objects.map(async obj => expect( ( await getObject(obj.className, obj.id, { @@ -4467,7 +4465,7 @@ describe('ParseGraphQLServer', () => { ) ); await Promise.all( - objects.map(async (obj) => + objects.map(async obj => expect( ( await getObject(obj.className, obj.id, { @@ -4478,7 +4476,7 @@ describe('ParseGraphQLServer', () => { ) ); await Promise.all( - objects.map(async (obj) => + objects.map(async obj => expect( ( await getObject(obj.className, obj.id, { @@ -4494,7 +4492,7 @@ describe('ParseGraphQLServer', () => { }) ).toBeRejectedWith(jasmine.stringMatching('Object not found')); await Promise.all( - [object1, object3, object4].map(async (obj) => + [object1, object3, object4].map(async obj => expect( ( await getObject(obj.className, obj.id, { @@ -4505,7 +4503,7 @@ describe('ParseGraphQLServer', () => { ) ); await Promise.all( - objects.slice(0, 3).map((obj) => + objects.slice(0, 3).map(obj => expectAsync( getObject(obj.className, obj.id, { 'X-Parse-Session-Token': user4.getSessionToken(), @@ -4521,7 +4519,7 @@ describe('ParseGraphQLServer', () => { ).data.get.someField ).toEqual('someValue4'); await Promise.all( - objects.slice(0, 2).map((obj) => + objects.slice(0, 2).map(obj => expectAsync( getObject(obj.className, obj.id, { 'X-Parse-Session-Token': user5.getSessionToken(), @@ -4646,7 +4644,7 @@ describe('ParseGraphQLServer', () => { ).toBeDefined(); }); - it('should respect protectedFields', async (done) => { + it('should respect protectedFields', async done => { await prepareData(); await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); @@ -4762,7 +4760,7 @@ describe('ParseGraphQLServer', () => { let foundUserClassReadPreference = false; databaseAdapter.database.serverConfig.cursor.calls .all() - .forEach((call) => { + .forEach(call => { if ( call.args[0].ns.collection.indexOf('GraphQLClass') >= 0 ) { @@ -4826,7 +4824,7 @@ describe('ParseGraphQLServer', () => { let foundUserClassReadPreference = false; databaseAdapter.database.serverConfig.cursor.calls .all() - .forEach((call) => { + .forEach(call => { if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; expect(call.args[0].options.readPreference.mode).toBe( @@ -4886,7 +4884,7 @@ describe('ParseGraphQLServer', () => { let foundUserClassReadPreference = false; databaseAdapter.database.serverConfig.cursor.calls .all() - .forEach((call) => { + .forEach(call => { if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; expect(call.args[0].options.readPreference.mode).toBe( @@ -4936,7 +4934,7 @@ describe('ParseGraphQLServer', () => { expect(result.data.customers.edges.length).toEqual(2); - result.data.customers.edges.forEach((resultObj) => { + result.data.customers.edges.forEach(resultObj => { const obj = resultObj.node.objectId === obj1.id ? obj1 : obj2; expect(resultObj.node.objectId).toEqual(obj.id); expect(resultObj.node.someField).toEqual(obj.get('someField')); @@ -4977,12 +4975,12 @@ describe('ParseGraphQLServer', () => { expect( (await findObjects('GraphQLClass')).data.find.edges.map( - (object) => object.node.someField + object => object.node.someField ) ).toEqual([]); expect( (await findObjects('PublicClass')).data.find.edges.map( - (object) => object.node.someField + object => object.node.someField ) ).toEqual(['someValue4']); expect( @@ -4991,39 +4989,39 @@ describe('ParseGraphQLServer', () => { 'X-Parse-Master-Key': 'test', }) ).data.find.edges - .map((object) => object.node.someField) + .map(object => object.node.someField) .sort() ).toEqual(['someValue1', 'someValue2', 'someValue3']); expect( ( await findObjects('PublicClass', { 'X-Parse-Master-Key': 'test', }) - ).data.find.edges.map((object) => object.node.someField) + ).data.find.edges.map(object => object.node.someField) ).toEqual(['someValue4']); expect( ( await findObjects('GraphQLClass', { 'X-Parse-Session-Token': user1.getSessionToken(), }) ).data.find.edges - .map((object) => object.node.someField) + .map(object => object.node.someField) .sort() ).toEqual(['someValue1', 'someValue2', 'someValue3']); expect( ( await findObjects('PublicClass', { 'X-Parse-Session-Token': user1.getSessionToken(), }) - ).data.find.edges.map((object) => object.node.someField) + ).data.find.edges.map(object => object.node.someField) ).toEqual(['someValue4']); expect( ( await findObjects('GraphQLClass', { 'X-Parse-Session-Token': user2.getSessionToken(), }) ).data.find.edges - .map((object) => object.node.someField) + .map(object => object.node.someField) .sort() ).toEqual(['someValue1', 'someValue2', 'someValue3']); expect( @@ -5032,22 +5030,22 @@ describe('ParseGraphQLServer', () => { 'X-Parse-Session-Token': user3.getSessionToken(), }) ).data.find.edges - .map((object) => object.node.someField) + .map(object => object.node.someField) .sort() ).toEqual(['someValue1', 'someValue3']); expect( ( await findObjects('GraphQLClass', { 'X-Parse-Session-Token': user4.getSessionToken(), }) - ).data.find.edges.map((object) => object.node.someField) + ).data.find.edges.map(object => object.node.someField) ).toEqual([]); expect( ( await findObjects('GraphQLClass', { 'X-Parse-Session-Token': user5.getSessionToken(), }) - ).data.find.edges.map((object) => object.node.someField) + ).data.find.edges.map(object => object.node.someField) ).toEqual(['someValue3']); }); @@ -5100,7 +5098,7 @@ describe('ParseGraphQLServer', () => { expect( result.data.graphQLClasses.edges - .map((object) => object.node.someField) + .map(object => object.node.someField) .sort() ).toEqual(['someValue1', 'someValue3']); }); @@ -5178,7 +5176,7 @@ describe('ParseGraphQLServer', () => { expect( result.data.graphQLClasses.edges - .map((object) => object.node.someField) + .map(object => object.node.someField) .sort() ).toEqual(['someValue1', 'someValue2']); }); @@ -5345,7 +5343,7 @@ describe('ParseGraphQLServer', () => { }); expect( - result.data.find.edges.map((obj) => obj.node.someField) + result.data.find.edges.map(obj => obj.node.someField) ).toEqual(['someValue14', 'someValue17']); }); @@ -5416,7 +5414,7 @@ describe('ParseGraphQLServer', () => { let result = await find(); expect( - result.data.someClasses.edges.map((edge) => edge.node.numberField) + result.data.someClasses.edges.map(edge => edge.node.numberField) ).toEqual(numberArray(0, 99)); expect(result.data.someClasses.count).toEqual(100); expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual( @@ -5432,7 +5430,7 @@ describe('ParseGraphQLServer', () => { result = await find({ first: 10 }); expect( - result.data.someClasses.edges.map((edge) => edge.node.numberField) + result.data.someClasses.edges.map(edge => edge.node.numberField) ).toEqual(numberArray(0, 9)); expect(result.data.someClasses.count).toEqual(100); expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual( @@ -5451,7 +5449,7 @@ describe('ParseGraphQLServer', () => { after: result.data.someClasses.pageInfo.endCursor, }); expect( - result.data.someClasses.edges.map((edge) => edge.node.numberField) + result.data.someClasses.edges.map(edge => edge.node.numberField) ).toEqual(numberArray(10, 19)); expect(result.data.someClasses.count).toEqual(100); expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual( @@ -5467,7 +5465,7 @@ describe('ParseGraphQLServer', () => { result = await find({ last: 10 }); expect( - result.data.someClasses.edges.map((edge) => edge.node.numberField) + result.data.someClasses.edges.map(edge => edge.node.numberField) ).toEqual(numberArray(90, 99)); expect(result.data.someClasses.count).toEqual(100); expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual( @@ -5486,7 +5484,7 @@ describe('ParseGraphQLServer', () => { before: result.data.someClasses.pageInfo.startCursor, }); expect( - result.data.someClasses.edges.map((edge) => edge.node.numberField) + result.data.someClasses.edges.map(edge => edge.node.numberField) ).toEqual(numberArray(80, 89)); expect(result.data.someClasses.count).toEqual(100); expect(result.data.someClasses.pageInfo.hasPreviousPage).toEqual( @@ -5820,7 +5818,7 @@ describe('ParseGraphQLServer', () => { let foundUserClassReadPreference = false; databaseAdapter.database.serverConfig.cursor.calls .all() - .forEach((call) => { + .forEach(call => { if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; expect(call.args[0].options.readPreference.mode).toBe( @@ -5877,7 +5875,7 @@ describe('ParseGraphQLServer', () => { let foundUserClassReadPreference = false; databaseAdapter.database.serverConfig.cursor.calls .all() - .forEach((call) => { + .forEach(call => { if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; expect(call.args[0].options.readPreference.mode).toBe( @@ -5937,7 +5935,7 @@ describe('ParseGraphQLServer', () => { let foundUserClassReadPreference = false; databaseAdapter.database.serverConfig.cursor.calls .all() - .forEach((call) => { + .forEach(call => { if (call.args[0].ns.collection.indexOf('GraphQLClass') >= 0) { foundGraphQLClassReadPreference = true; expect(call.args[0].options.readPreference.mode).toBe( @@ -6008,7 +6006,7 @@ describe('ParseGraphQLServer', () => { let foundUserClassReadPreference = false; databaseAdapter.database.serverConfig.cursor.calls .all() - .forEach((call) => { + .forEach(call => { if ( call.args[0].ns.collection.indexOf('GraphQLClass') >= 0 ) { @@ -6067,7 +6065,7 @@ describe('ParseGraphQLServer', () => { } expect( - result.data.graphQLClasses.edges.map((edge) => edge.node.objectId) + result.data.graphQLClasses.edges.map(edge => edge.node.objectId) ).toEqual([object3.id, object1.id, object2.id]); }); @@ -6120,7 +6118,7 @@ describe('ParseGraphQLServer', () => { expect( result.data.parentClass.graphQLClasses.edges.map( - (edge) => edge.node.objectId + edge => edge.node.objectId ) ).toEqual([object3.id, object1.id, object2.id]); } @@ -6384,7 +6382,7 @@ describe('ParseGraphQLServer', () => { } await Promise.all( - objects.slice(0, 3).map(async (obj) => { + objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( updateObject(obj.className, obj.id, { @@ -6405,7 +6403,7 @@ describe('ParseGraphQLServer', () => { await object4.fetch({ useMasterKey: true }); expect(object4.get('someField')).toEqual('changedValue1'); await Promise.all( - objects.map(async (obj) => { + objects.map(async obj => { expect( ( await updateObject( @@ -6421,7 +6419,7 @@ describe('ParseGraphQLServer', () => { }) ); await Promise.all( - objects.map(async (obj) => { + objects.map(async obj => { expect( ( await updateObject( @@ -6437,7 +6435,7 @@ describe('ParseGraphQLServer', () => { }) ); await Promise.all( - objects.map(async (obj) => { + objects.map(async obj => { expect( ( await updateObject( @@ -6453,7 +6451,7 @@ describe('ParseGraphQLServer', () => { }) ); await Promise.all( - [object1, object3, object4].map(async (obj) => { + [object1, object3, object4].map(async obj => { expect( ( await updateObject( @@ -6480,7 +6478,7 @@ describe('ParseGraphQLServer', () => { await object2.fetch({ useMasterKey: true }); expect(object2.get('someField')).toEqual(originalFieldValue); await Promise.all( - objects.slice(0, 3).map(async (obj) => { + objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( updateObject( @@ -6507,7 +6505,7 @@ describe('ParseGraphQLServer', () => { await object4.fetch({ useMasterKey: true }); expect(object4.get('someField')).toEqual('changedValue6'); await Promise.all( - objects.slice(0, 2).map(async (obj) => { + objects.slice(0, 2).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( updateObject( @@ -6583,7 +6581,7 @@ describe('ParseGraphQLServer', () => { } await Promise.all( - objects.slice(0, 3).map(async (obj) => { + objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( updateObject(obj.className, obj.id, { @@ -6607,7 +6605,7 @@ describe('ParseGraphQLServer', () => { await object4.fetch({ useMasterKey: true }); expect(object4.get('someField')).toEqual('changedValue1'); await Promise.all( - objects.map(async (obj) => { + objects.map(async obj => { expect( ( await updateObject( @@ -6626,7 +6624,7 @@ describe('ParseGraphQLServer', () => { }) ); await Promise.all( - objects.map(async (obj) => { + objects.map(async obj => { expect( ( await updateObject( @@ -6645,7 +6643,7 @@ describe('ParseGraphQLServer', () => { }) ); await Promise.all( - objects.map(async (obj) => { + objects.map(async obj => { expect( ( await updateObject( @@ -6664,7 +6662,7 @@ describe('ParseGraphQLServer', () => { }) ); await Promise.all( - [object1, object3, object4].map(async (obj) => { + [object1, object3, object4].map(async obj => { expect( ( await updateObject( @@ -6694,7 +6692,7 @@ describe('ParseGraphQLServer', () => { await object2.fetch({ useMasterKey: true }); expect(object2.get('someField')).toEqual(originalFieldValue); await Promise.all( - objects.slice(0, 3).map(async (obj) => { + objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( updateObject( @@ -6724,7 +6722,7 @@ describe('ParseGraphQLServer', () => { await object4.fetch({ useMasterKey: true }); expect(object4.get('someField')).toEqual('changedValue6'); await Promise.all( - objects.slice(0, 2).map(async (obj) => { + objects.slice(0, 2).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( updateObject( @@ -6851,7 +6849,7 @@ describe('ParseGraphQLServer', () => { } await Promise.all( - objects.slice(0, 3).map(async (obj) => { + objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( deleteObject(obj.className, obj.id) @@ -6861,7 +6859,7 @@ describe('ParseGraphQLServer', () => { }) ); await Promise.all( - objects.slice(0, 3).map(async (obj) => { + objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( deleteObject(obj.className, obj.id, { @@ -6952,7 +6950,7 @@ describe('ParseGraphQLServer', () => { } await Promise.all( - objects.slice(0, 3).map(async (obj) => { + objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( deleteObject(obj.className, obj.id) @@ -6962,7 +6960,7 @@ describe('ParseGraphQLServer', () => { }) ); await Promise.all( - objects.slice(0, 3).map(async (obj) => { + objects.slice(0, 3).map(async obj => { const originalFieldValue = obj.get('someField'); await expectAsync( deleteObject(obj.className, obj.id, { @@ -7185,6 +7183,56 @@ describe('ParseGraphQLServer', () => { expect(resultFoo).toBeDefined(); expect(resultFoo.bar).toEqual('hello'); }); + it('should return logged user and do not by pass pointer security', async () => { + const masterKeyOnlyACL = new Parse.ACL(); + masterKeyOnlyACL.setPublicReadAccess(false); + masterKeyOnlyACL.setPublicWriteAccess(false); + const foo = new Parse.Object('Foo'); + foo.setACL(masterKeyOnlyACL); + foo.set('bar', 'hello'); + await foo.save(null, { useMasterKey: true }); + const userName = 'userx1', + password = 'user1', + email = 'emailUserx1@parse.com'; + + const user = new Parse.User(); + user.setUsername(userName); + user.setPassword(password); + user.setEmail(email); + user.set('userFoo', foo); + await user.signUp(); + + await parseGraphQLServer.parseGraphQLSchema.databaseController.schemaCache.clear(); + + const session = await Parse.Session.current(); + const result = await apolloClient.query({ + query: gql` + query GetCurrentUser { + viewer { + sessionToken + user { + id + objectId + userFoo { + bar + } + } + } + } + `, + context: { + headers: { + 'X-Parse-Session-Token': session.getSessionToken(), + }, + }, + }); + + const sessionToken = result.data.viewer.sessionToken; + const { objectId, userFoo: resultFoo } = result.data.viewer.user; + expect(objectId).toEqual(user.id); + expect(sessionToken).toBeDefined(); + expect(resultFoo).toEqual(null); + }); }); describe('Users Mutations', () => { @@ -7635,8 +7683,8 @@ describe('ParseGraphQLServer', () => { } }); - it('should accept different params', (done) => { - Parse.Cloud.define('hello', async (req) => { + it('should accept different params', done => { + Parse.Cloud.define('hello', async req => { expect(req.params.date instanceof Date).toBe(true); expect(req.params.date.getTime()).toBe(1463907600000); expect(req.params.dateList[0] instanceof Date).toBe(true); @@ -7772,7 +7820,7 @@ describe('ParseGraphQLServer', () => { ).data['__type']; expect(functionEnum.kind).toEqual('ENUM'); expect( - functionEnum.enumValues.map((value) => value.name).sort() + functionEnum.enumValues.map(value => value.name).sort() ).toEqual(['_underscored', 'a', 'b', 'contains1Number']); } catch (e) { handleError(e); @@ -7814,12 +7862,12 @@ describe('ParseGraphQLServer', () => { ).data['__type']; expect(functionEnum.kind).toEqual('ENUM'); expect( - functionEnum.enumValues.map((value) => value.name).sort() + functionEnum.enumValues.map(value => value.name).sort() ).toEqual(['a']); expect( parseGraphQLServer.parseGraphQLSchema.log.warn.calls .all() - .map((call) => call.args[0]) + .map(call => call.args[0]) .sort() ).toEqual([ 'Function 1NumberInTheBeggning could not be added to the auto schema because GraphQL names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/.', @@ -8715,13 +8763,13 @@ describe('ParseGraphQLServer', () => { expect(result.name).toEqual('imACountry2'); expect(result.companies.edges.length).toEqual(3); expect( - result.companies.edges.some((o) => o.node.objectId === company.id) + result.companies.edges.some(o => o.node.objectId === company.id) ).toBeTruthy(); expect( - result.companies.edges.some((o) => o.node.name === 'imACompany2') + result.companies.edges.some(o => o.node.name === 'imACompany2') ).toBeTruthy(); expect( - result.companies.edges.some((o) => o.node.name === 'imACompany3') + result.companies.edges.some(o => o.node.name === 'imACompany3') ).toBeTruthy(); } ); @@ -8806,16 +8854,16 @@ describe('ParseGraphQLServer', () => { expect(result.companies.edges.length).toEqual(2); expect( result.companies.edges.some( - (c) => + c => c.node.name === 'imACompany2' && - c.node.teams.edges.some((t) => t.node.name === 'imATeam2') + c.node.teams.edges.some(t => t.node.name === 'imATeam2') ) ).toBeTruthy(); expect( result.companies.edges.some( - (c) => + c => c.node.name === 'imACompany3' && - c.node.teams.edges.some((t) => t.node.name === 'imATeam3') + c.node.teams.edges.some(t => t.node.name === 'imATeam3') ) ).toBeTruthy(); }); @@ -8884,17 +8932,13 @@ describe('ParseGraphQLServer', () => { expect(result.objectId).toEqual(country.id); expect(result.companies.edges.length).toEqual(2); expect( - result.companies.edges.some( - (o) => o.node.objectId === company2.id - ) + result.companies.edges.some(o => o.node.objectId === company2.id) ).toBeTruthy(); expect( - result.companies.edges.some((o) => o.node.name === 'imACompany3') + result.companies.edges.some(o => o.node.name === 'imACompany3') ).toBeTruthy(); expect( - result.companies.edges.some( - (o) => o.node.objectId === company1.id - ) + result.companies.edges.some(o => o.node.objectId === company1.id) ).toBeFalsy(); } ); @@ -8966,7 +9010,7 @@ describe('ParseGraphQLServer', () => { expect(result.name).toEqual('imACountry2'); expect(result.companies.edges.length).toEqual(1); expect( - result.companies.edges.some((o) => o.node.name === 'imACompany2') + result.companies.edges.some(o => o.node.name === 'imACompany2') ).toBeTruthy(); } ); @@ -9017,10 +9061,10 @@ describe('ParseGraphQLServer', () => { expect(result1.objectId).toEqual(country.id); expect(result1.companies.edges.length).toEqual(2); expect( - result1.companies.edges.some((o) => o.node.objectId === company1.id) + result1.companies.edges.some(o => o.node.objectId === company1.id) ).toBeTruthy(); expect( - result1.companies.edges.some((o) => o.node.objectId === company2.id) + result1.companies.edges.some(o => o.node.objectId === company2.id) ).toBeTruthy(); // With where @@ -9762,12 +9806,12 @@ describe('ParseGraphQLServer', () => { const { edges } = someClasses; expect(edges.length).toEqual(2); expect( - edges.find((result) => result.node.id === create1.someClass.id) - .node.someField + edges.find(result => result.node.id === create1.someClass.id).node + .someField ).toEqual(someFieldValue); expect( - edges.find((result) => result.node.id === create2.someClass.id) - .node.someField + edges.find(result => result.node.id === create2.someClass.id).node + .someField ).toEqual(someFieldValue2); } catch (e) { handleError(e); @@ -9859,7 +9903,7 @@ describe('ParseGraphQLServer', () => { const { someField } = getResult.data.someClass; expect(Array.isArray(someField)).toBeTruthy(); - expect(someField.map((element) => element.value)).toEqual( + expect(someField.map(element => element.value)).toEqual( someFieldValue ); expect(getResult.data.someClasses.edges.length).toEqual(1); @@ -10276,7 +10320,7 @@ describe('ParseGraphQLServer', () => { [46, 47], [48, 49], [44, 45], - ].map((point) => ({ + ].map(point => ({ latitude: point[0], longitude: point[1], })); @@ -10356,7 +10400,7 @@ describe('ParseGraphQLServer', () => { 'object' ); expect(getResult.data.someClass.somePolygonField).toEqual( - somePolygonFieldValue.map((geoPoint) => ({ + somePolygonFieldValue.map(geoPoint => ({ ...geoPoint, __typename: 'GeoPoint', })) @@ -10672,7 +10716,7 @@ describe('ParseGraphQLServer', () => { `, }); parseGraphQLServer.applyGraphQL(expressApp); - await new Promise((resolve) => + await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve) ); const httpLink = createUploadLink({ @@ -10797,7 +10841,7 @@ describe('ParseGraphQLServer', () => { fields: { nameUpperCase: { type: new GraphQLNonNull(GraphQLString), - resolve: (p) => p.name.toUpperCase(), + resolve: p => p.name.toUpperCase(), }, type: { type: TypeEnum }, language: { @@ -10858,7 +10902,7 @@ describe('ParseGraphQLServer', () => { }); parseGraphQLServer.applyGraphQL(expressApp); - await new Promise((resolve) => + await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve) ); const httpLink = createUploadLink({ @@ -10992,7 +11036,7 @@ describe('ParseGraphQLServer', () => { }); parseGraphQLServer.applyGraphQL(expressApp); - await new Promise((resolve) => + await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve) ); const httpLink = createUploadLink({
src/GraphQL/loaders/usersMutations.js+14 −18 modified@@ -41,23 +41,22 @@ const load = parseGraphQLSchema => { const { fields } = args; const { config, auth, info } = context; - const { sessionToken } = await objectsMutations.createObject( + const { sessionToken, objectId } = await objectsMutations.createObject( '_User', fields, config, auth, info ); - info.sessionToken = sessionToken; + context.info.sessionToken = sessionToken; return { viewer: await getUserFromSessionToken( - config, - info, + context, mutationInfo, 'viewer.user.', - true + objectId ), }; } catch (e) { @@ -120,23 +119,22 @@ const load = parseGraphQLSchema => { const { fields, authData } = args; const { config, auth, info } = context; - const { sessionToken } = await objectsMutations.createObject( + const { sessionToken, objectId } = await objectsMutations.createObject( '_User', { ...fields, authData }, config, auth, info ); - info.sessionToken = sessionToken; + context.info.sessionToken = sessionToken; return { viewer: await getUserFromSessionToken( - config, - info, + context, mutationInfo, 'viewer.user.', - true + objectId ), }; } catch (e) { @@ -183,7 +181,7 @@ const load = parseGraphQLSchema => { const { username, password } = args; const { config, auth, info } = context; - const { sessionToken } = ( + const { sessionToken, objectId } = ( await usersRouter.handleLogIn({ body: { username, @@ -196,15 +194,14 @@ const load = parseGraphQLSchema => { }) ).response; - info.sessionToken = sessionToken; + context.info.sessionToken = sessionToken; return { viewer: await getUserFromSessionToken( - config, - info, + context, mutationInfo, 'viewer.user.', - true + objectId ), }; } catch (e) { @@ -236,11 +233,10 @@ const load = parseGraphQLSchema => { const { config, auth, info } = context; const viewer = await getUserFromSessionToken( - config, - info, + context, mutationInfo, 'viewer.user.', - true + auth.user.id ); await usersRouter.handleLogOut({
src/GraphQL/loaders/usersQueries.js+25 −20 modified@@ -2,16 +2,16 @@ import { GraphQLNonNull } from 'graphql'; import getFieldNames from 'graphql-list-fields'; import Parse from 'parse/node'; import rest from '../../rest'; -import Auth from '../../Auth'; import { extractKeysAndInclude } from './parseClassTypes'; +import { Auth } from '../../Auth'; const getUserFromSessionToken = async ( - config, - info, + context, queryInfo, keysPrefix, - validatedToken + userId ) => { + const { info, config } = context; if (!info || !info.sessionToken) { throw new Parse.Error( Parse.Error.INVALID_SESSION_TOKEN, @@ -27,48 +27,55 @@ const getUserFromSessionToken = async ( const { keys } = keysAndInclude; let { include } = keysAndInclude; - if (validatedToken && !keys && !include) { + if (userId && !keys && !include) { return { sessionToken, }; } else if (keys && !include) { include = 'user'; } + if (userId) { + // We need to re create the auth context + // to avoid security breach if userId is provided + context.auth = new Auth({ + config, + isMaster: context.auth.isMaster, + user: { id: userId }, + }); + } + const options = {}; if (keys) { options.keys = keys .split(',') - .map(key => `user.${key}`) + .map(key => `${key}`) .join(','); } if (include) { options.include = include .split(',') - .map(included => `user.${included}`) + .map(included => `${included}`) .join(','); } const response = await rest.find( config, - Auth.master(config), - '_Session', - { sessionToken }, + context.auth, + '_User', + // Get the user it self from auth object + { objectId: context.auth.user.id }, options, info.clientVersion, - info.context, + info.context ); - if ( - !response.results || - response.results.length == 0 || - !response.results[0].user - ) { + if (!response.results || response.results.length == 0) { throw new Parse.Error( Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token' ); } else { - const user = response.results[0].user; + const user = response.results[0]; return { sessionToken, user, @@ -89,10 +96,8 @@ const load = parseGraphQLSchema => { type: new GraphQLNonNull(parseGraphQLSchema.viewerType), async resolve(_source, _args, context, queryInfo) { try { - const { config, info } = context; return await getUserFromSessionToken( - config, - info, + context, queryInfo, 'user.', false
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-236h-rqv8-8q73ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-15126ghsaADVISORY
- github.com/parse-community/parse-server/blob/master/CHANGELOG.mdghsax_refsource_MISCWEB
- github.com/parse-community/parse-server/commit/78239ac9071167fdf243c55ae4bc9a2c0b0d89aaghsax_refsource_MISCWEB
- github.com/parse-community/parse-server/security/advisories/GHSA-236h-rqv8-8q73ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.