Skip to content

fix(parser): limit filter nesting depth and length, plus small fixes and doc cleanups - #109

Merged
pdevito3 merged 11 commits into
mainfrom
fm/qk-dos-limits
Sep 29, 2026
Merged

pdevito3 merged 11 commits into
mainfrom
fm/qk-dos-limits

Conversation

@pdevito3

Copy link
Copy Markdown
Owner

Summary

  • Add a nesting-depth limit and an input-length limit to the filter parser. Before this fix, a deeply nested filter (for example, thousands of ( characters) crashed the process with unbounded memory growth and CPU use, before any real parsing began. Both limits are enabled by default, and both are configurable through QueryKitSettings / QueryKitConfiguration, matching the existing MaxPropertyDepth pattern. A limit violation throws QueryKitNestingDepthExceededException or QueryKitInputLengthExceededException (both QueryKitException subtypes) before parsing starts.
  • Fix ComparisonOperator.HasType's error message: it said "DoesNotHaveType is only supported for collections" (copy-paste error). It now correctly says "HasType is only supported for collections".
  • Remove the unused ArithmeticOperator.FromSymbol method.
  • Remove a stray Console.WriteLine debug statement left in a HasConversion test.
  • Delete three HasConversion tests that were skipped as "out of date." Verified live that each one fails against current, correct behavior, and that HasConversionTests.cs already covers the same scenarios correctly.
  • Fix two flaky tests that used faker.Lorem.Word() for an "unknown property" case, which can occasionally collide with a real property name. Both now use a fixed, clearly-fake property name.
  • CLAUDE.md: update the supported-TFM list to include net10.0.
  • README.md: fix a broken case-insensitive-appendix example (eq$ should be eqi for the shown config), and fix two code snippets that did not compile (malformed string literals).

Verification

Reproduced the DoS with the verify-querykit skill before the fix: a filter with 2,000 nested parentheses drove the driver process to 3.4 GB RSS and 76.7% CPU with no return. After the fix, the same input throws QueryKitNestingDepthExceededException in under 1 second.

Every commit leaves QueryKit.UnitTests (213 tests) and QueryKit.IntegrationTests (212 tests, real Postgres via Testcontainers) fully green.

Out of scope

This PR intentionally does not include:

These come from the same review pass and are tracked separately for follow-up PRs.

Test plan

  • dotnet test QueryKit.UnitTests/ — 213 passed, 0 skipped
  • dotnet test QueryKit.IntegrationTests/ — 212 passed, 0 skipped
  • Live before/after reproduction of the nesting-depth DoS via verify-querykit

@pdevito3 pdevito3 changed the title fix: deep-nesting DoS in the filter parser, plus small bug fixes and doc cleanups fix(parser): limit filter nesting depth and length, plus small fixes and doc cleanups Sep 29, 2026
…gth limit

Deeply nested parentheses in a filter string can exhaust CPU and memory
during parsing, or overflow the call stack, before any grammar rule runs.
A crafted filter of a few thousand nested groups can hang a process
indefinitely. Check the input length and parenthesis nesting depth before
the parser sees the string, and reject input over a limit. The limits are
configurable through QueryKitSettings, with safe defaults enabled out of
the box (depth 32, length 5000).
…nknown-property tests

Bogus can generate the word "id", a real property on TestingPerson, about
once every 182 runs. When it does, the test asserts an UnknownFilterPropertyException
that never gets thrown, and the test fails at random. Use a fixed name that can
never collide with a real property.
The HasType operator (^$) raised an error message that named
DoesNotHaveType instead of itself, copied from the operator below it.
No code in the library or the tests calls this method.
Use ToDisplayString() instead of ToString() so the assertion still
sees the literal value after PR 110 parameterized filter values.
Use eqi (matching the configured appendix) instead of eq$ so the example actually parses.
The filter example used a malformed verbatim-interpolated string, and the
sort example mixed interpolation with an unclosed raw string. Both examples
now compile and run as written.
The build step used --configuration Release, but the test step had no
configuration flag, so it built and ran a fresh Debug build instead.
The test step now passes --configuration Release, so CI tests the same
build that dotnet pack ships.
Title == salt does not throw a ParsingException. QueryKit reads the
unquoted word as the literal text salt.
@pdevito3
pdevito3 merged commit de350f7 into main Sep 29, 2026
2 checks passed
@pdevito3
pdevito3 deleted the fm/qk-dos-limits branch September 29, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant