Skip to content

fix(filter)!: apply PreventFilter and PreventSort to every property path - #136

Merged
pdevito3 merged 2 commits into
v2from
fm/qk-breaking-prevent-bypass
Oct 3, 2026
Merged

pdevito3 merged 2 commits into
v2from
fm/qk-breaking-prevent-bypass

Conversation

@pdevito3

@pdevito3 pdevito3 commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • The parser resolves each property reference with PropertyResolver and checks PreventFilter in arithmetic, on the right side, in a property list, and on the left side. The lookup ignores the letter case.
  • A derived property and a custom operation with PreventFilter are checked too.
  • SortParser checks PreventSort on the resolved member. Another letter case, or the member name of a property with a query name, is also checked. A derived property with PreventSort is checked too.
  • The right-side property and the sort expression are built from the resolved reference.Path, not from the raw text. As a result, the checked property is always the property that QueryKit compares or sorts by.
  • A prevented clause obeys IgnoredClauseBehavior (removed by default on v2). A prevented sort is skipped.
  • A property list uses the case setting of the resolved member path.

v1.14.2 behavior

PreventFilter was checked only for a left-side member, by its name in the exact case. PreventSort was checked by the typed path in the exact case. A caller could filter or sort by a prevented property in six ways:

Case Input Configuration v1.14.2 This PR
I1 (Age + 0) > 30 Age PreventFilter filtered by Age clause ignored
I2 FirstName == Secret Secret PreventFilter x => (x.FirstName == x.Secret) clause ignored
I3 (secret, FirstName) @=* "s" Secret PreventFilter filtered by Secret only FirstName
I4 secret == "s" Secret PreventFilter, query name hidden x => (x.Secret == "s") clause ignored
I5 fullName == "Ann Lee" derived property with PreventFilter filtered by the derived value clause ignored
I6 sort age desc Age PreventSort, query name years sorted by Age sort skipped

A custom operation with PreventFilter was also still applied.

Security risk on v1.14.2

With any of these bypasses, a caller can find the value of a hidden field one comparison at a time, for example (Salary + 0) > 50000, then > 75000. A sort bypass shows the order of the hidden values.

Migration

No setting brings back the old behavior. If a caller needs to filter by a property, remove PreventFilter from it.

Rebase on v2

Tests

Part of #132.

PreventFilter was checked only for a left-side member, by its name in
the exact case after the query-name rewrite. Arithmetic, the right side
of a comparison, a property list in another case, derived properties,
and custom operations skipped the check. PreventSort was checked by the
typed path in the exact case. A caller could learn the value of a
hidden field one comparison at a time.

The parser now resolves each property reference and applies the
prevent settings in each of these places. A prevented clause follows
IgnoredClauseBehavior, and a prevented sort is skipped. Arithmetic
does not apply MaxPropertyDepth in this change.

BREAKING CHANGE: a filter or sort that reaches a property with
PreventFilter or PreventSort through arithmetic, the right side, a
property list, another letter case, the member name of a property with
a query name, a derived property, or a custom operation no longer
filters or sorts by that property. The clause follows
IgnoredClauseBehavior, and the sort is skipped.
@pdevito3
pdevito3 force-pushed the fm/qk-breaking-prevent-bypass branch from a62bbeb to a571d97 Compare October 3, 2026 11:42
@pdevito3
pdevito3 merged commit df67dae into v2 Oct 3, 2026
2 checks passed
@pdevito3
pdevito3 deleted the fm/qk-breaking-prevent-bypass branch October 3, 2026 11:44
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