Skip to content

Commit 12c4608

Browse files
authored
test: Request complexity controls correctly enforce subquery depth limits (GHSA-rqxg-pcc9-f79w) (#10215)
1 parent 8a0bea1 commit 12c4608

File tree

2 files changed

+121
-0
lines changed

2 files changed

+121
-0
lines changed

spec/GraphQLQueryComplexity.spec.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,38 @@ describe('graphql query complexity', () => {
178178
expect(result.errors).toBeUndefined();
179179
});
180180
});
181+
182+
describe('where argument breadth', () => {
183+
it('should enforce depth and field limits regardless of where argument breadth', async () => {
184+
await setupGraphQL({
185+
requestComplexity: { graphQLDepth: 3, graphQLFields: 200, subqueryDepth: 1 },
186+
});
187+
// The GraphQL where argument may contain many OR branches, but the
188+
// complexity check correctly measures the selection set depth/fields,
189+
// not the where variable content. A query exceeding graphQLDepth is
190+
// rejected even when the where argument is simple.
191+
const result = await graphqlRequest(buildDeepQuery());
192+
expect(result.errors).toBeDefined();
193+
expect(result.errors[0].message).toMatch(
194+
/GraphQL query depth of \d+ exceeds maximum allowed depth of 3/
195+
);
196+
});
197+
198+
it('should allow query with wide where argument when selection set is within limits', async () => {
199+
await setupGraphQL({
200+
requestComplexity: { graphQLDepth: 10, graphQLFields: 200, subqueryDepth: 1 },
201+
});
202+
203+
const obj = new Parse.Object('TestItem');
204+
obj.set('name', 'test');
205+
await obj.save();
206+
207+
// Wide where with many OR branches — complexity check measures selection
208+
// set depth and field count, not where argument structure
209+
const orBranches = Array.from({ length: 20 }, (_, i) => `{ name: { equalTo: "test${i}" } }`).join(', ');
210+
const query = `{ testItems(where: { OR: [${orBranches}] }) { edges { node { objectId } } } }`;
211+
const result = await graphqlRequest(query);
212+
expect(result.errors).toBeUndefined();
213+
});
214+
});
181215
});

spec/RequestComplexity.spec.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,93 @@ describe('request complexity', () => {
242242
rest.find(config, auth.nobody(config), '_User', where)
243243
).toBeResolved();
244244
});
245+
246+
it('should allow multiple sibling $inQuery at same depth within limit', async () => {
247+
await reconfigureServer({
248+
requestComplexity: { subqueryDepth: 1 },
249+
});
250+
config = Config.get('test');
251+
// Multiple sibling $inQuery operators in $or, each at depth 1 — within the limit
252+
const where = {
253+
$or: [
254+
{ username: { $inQuery: { className: '_User', where: { username: 'a' } } } },
255+
{ username: { $inQuery: { className: '_User', where: { username: 'b' } } } },
256+
{ username: { $inQuery: { className: '_User', where: { username: 'c' } } } },
257+
],
258+
};
259+
await expectAsync(
260+
rest.find(config, auth.nobody(config), '_User', where)
261+
).toBeResolved();
262+
});
263+
264+
it('should reject sibling $inQuery when nested beyond depth limit', async () => {
265+
await reconfigureServer({
266+
requestComplexity: { subqueryDepth: 1 },
267+
});
268+
config = Config.get('test');
269+
// Each sibling contains a nested $inQuery at depth 2 — exceeds limit
270+
const where = {
271+
$or: [
272+
{
273+
username: {
274+
$inQuery: {
275+
className: '_User',
276+
where: { username: { $inQuery: { className: '_User', where: {} } } },
277+
},
278+
},
279+
},
280+
{
281+
username: {
282+
$inQuery: {
283+
className: '_User',
284+
where: { username: { $inQuery: { className: '_User', where: {} } } },
285+
},
286+
},
287+
},
288+
],
289+
};
290+
await expectAsync(
291+
rest.find(config, auth.nobody(config), '_User', where)
292+
).toBeRejectedWith(
293+
jasmine.objectContaining({
294+
message: jasmine.stringMatching(/Subquery nesting depth exceeds maximum allowed depth of 1/),
295+
})
296+
);
297+
});
298+
299+
it('should allow multiple sibling $notInQuery at same depth within limit', async () => {
300+
await reconfigureServer({
301+
requestComplexity: { subqueryDepth: 1 },
302+
});
303+
config = Config.get('test');
304+
const where = {
305+
$or: [
306+
{ username: { $notInQuery: { className: '_User', where: { username: 'a' } } } },
307+
{ username: { $notInQuery: { className: '_User', where: { username: 'b' } } } },
308+
{ username: { $notInQuery: { className: '_User', where: { username: 'c' } } } },
309+
],
310+
};
311+
await expectAsync(
312+
rest.find(config, auth.nobody(config), '_User', where)
313+
).toBeResolved();
314+
});
315+
316+
it('should allow mixed sibling $inQuery and $notInQuery at same depth within limit', async () => {
317+
await reconfigureServer({
318+
requestComplexity: { subqueryDepth: 1 },
319+
});
320+
config = Config.get('test');
321+
const where = {
322+
$or: [
323+
{ username: { $inQuery: { className: '_User', where: { username: 'a' } } } },
324+
{ username: { $notInQuery: { className: '_User', where: { username: 'b' } } } },
325+
{ username: { $inQuery: { className: '_User', where: { username: 'c' } } } },
326+
],
327+
};
328+
await expectAsync(
329+
rest.find(config, auth.nobody(config), '_User', where)
330+
).toBeResolved();
331+
});
245332
});
246333

247334
describe('query depth', () => {

0 commit comments

Comments
 (0)