Skip to content

Commit 42705c6

Browse files
authored
Merge branch 'main' into renovate/envelop
2 parents d8c5695 + 9989861 commit 42705c6

File tree

5 files changed

+91
-11
lines changed

5 files changed

+91
-11
lines changed

.changeset/bright-seals-relate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@escape.tech/graphql-armor-max-depth": patch
3+
---
4+
5+
fix: max-depth bypass using fragment cache

.changeset/hip-cheetahs-eat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@escape.tech/graphql-armor-max-depth": patch
3+
---
4+
5+
fix: max-depth ignore only Field Node named __schema

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
],
5353
"volta": {
5454
"node": "24.2.0",
55-
"yarn": "1.22.22"
55+
"yarn": "4.9.2"
5656
},
5757
"engines": {
5858
"node": ">=18.0.0"

packages/plugins/max-depth/src/index.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ class MaxDepthVisitor {
7676
node: FieldNode | FragmentDefinitionNode | InlineFragmentNode | OperationDefinitionNode | FragmentSpreadNode,
7777
parentDepth = 0,
7878
): number {
79-
if (this.config.ignoreIntrospection && 'name' in node && node.name?.value === '__schema') {
79+
if (
80+
this.config.ignoreIntrospection &&
81+
'name' in node &&
82+
node.name?.value === '__schema' &&
83+
node.kind === Kind.FIELD
84+
) {
8085
return 0;
8186
}
8287
let depth = parentDepth;
@@ -93,20 +98,20 @@ class MaxDepthVisitor {
9398
}
9499
}
95100
} else if (node.kind == Kind.FRAGMENT_SPREAD) {
101+
if (!this.config.flattenFragments) {
102+
parentDepth += 1;
103+
}
104+
96105
if (this.visitedFragments.has(node.name.value)) {
97-
return this.visitedFragments.get(node.name.value) ?? 0;
106+
return parentDepth + (this.visitedFragments.get(node.name.value) ?? 0);
98107
} else {
99108
this.visitedFragments.set(node.name.value, -1);
100109
}
101110
const fragment = this.context.getFragment(node.name.value);
102111
if (fragment) {
103-
let fragmentDepth;
104-
if (this.config.flattenFragments) {
105-
fragmentDepth = this.countDepth(fragment, parentDepth);
106-
} else {
107-
fragmentDepth = this.countDepth(fragment, parentDepth + 1);
108-
}
109-
depth = Math.max(depth, fragmentDepth);
112+
let fragmentDepth = this.countDepth(fragment, 0);
113+
114+
depth = Math.max(depth, parentDepth + fragmentDepth);
110115
if (this.visitedFragments.get(node.name.value) === -1) {
111116
this.visitedFragments.set(node.name.value, fragmentDepth);
112117
}

packages/plugins/max-depth/test/index.spec.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ describe('maxDepthPlugin', () => {
191191
assertSingleExecutionValue(result);
192192
expect(result.errors).toBeDefined();
193193
expect(result.errors?.map((error) => error.message)).toContain(
194-
'Syntax Error: Query depth limit of 3 exceeded, found 4.',
194+
'Syntax Error: Query depth limit of 3 exceeded, found 5.',
195195
);
196196
});
197197

@@ -239,4 +239,69 @@ describe('maxDepthPlugin', () => {
239239
`Syntax Error: Query depth limit of ${maxDepth} exceeded, found ${maxDepth + 1}.`,
240240
]);
241241
});
242+
243+
it('rejects for fragment named `__schema` exceeding max depth', async () => {
244+
const bypass_query = `
245+
query {
246+
books {
247+
author {
248+
books {
249+
author {
250+
...__schema
251+
}
252+
}
253+
}
254+
}
255+
}
256+
fragment __schema on Author {
257+
books {
258+
title
259+
}
260+
}
261+
`;
262+
const maxDepth = 6;
263+
const testkit = createTestkit([maxDepthPlugin({ n: maxDepth, exposeLimits: true })], schema);
264+
const result = await testkit.execute(bypass_query);
265+
266+
assertSingleExecutionValue(result);
267+
expect(result.errors).toBeDefined();
268+
expect(result.errors?.map((error) => error.message)).toEqual([
269+
`Syntax Error: Query depth limit of ${maxDepth} exceeded, found ${maxDepth + 2}.`,
270+
]);
271+
});
272+
273+
it('rejects for exceeding max depth by reusing a cached Fragment', async () => {
274+
const bypass_query = `
275+
query {
276+
books {
277+
author {
278+
...Test
279+
}
280+
}
281+
books {
282+
author {
283+
books {
284+
author {
285+
...Test
286+
}
287+
}
288+
}
289+
}
290+
}
291+
fragment Test on Author {
292+
books {
293+
title
294+
}
295+
}
296+
`;
297+
const maxDepth = 6;
298+
const testkit = createTestkit([maxDepthPlugin({ n: maxDepth, exposeLimits: true })], schema);
299+
const result = await testkit.execute(bypass_query);
300+
301+
assertSingleExecutionValue(result);
302+
expect(result.errors).toBeDefined();
303+
expect(result.errors?.map((error) => error.message)).toEqual([
304+
`Syntax Error: Query depth limit of ${maxDepth} exceeded, found ${maxDepth + 2}.`,
305+
]);
306+
});
242307
});

0 commit comments

Comments
 (0)