Conversation
This was referenced Oct 1, 2026
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
force-pushed
the
fm/qk-breaking-prevent-bypass
branch
from
October 1, 2026 21:35
fc9011c to
a62bbeb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For later consideration in a major version. Do not merge now. #134 opened these bypasses again to keep v1.x compatible with v1.14.2 (restore commit
2f33f60). This PR closes them again. It re-applies the permission parts of988fdff,2781423,630d080,1f40773, andb350bf8(#113). The captain decides on this PR separately.Summary
PropertyResolverand checksPreventFilterin arithmetic, on the right side, in a property list, and on the left side. The lookup ignores the letter case.PreventFilterare checked too.SortParserchecksPreventSorton the resolved member, so another letter case or the member name of a property with a query name is also checked. A derived property withPreventSortis checked too.IgnoredClauseBehavior(true == trueby default, or removed). A prevented sort is skipped.Scope: arithmetic in this PR checks only the prevent settings. It does not apply
MaxPropertyDepth. That is item P, in its own PR.v1.14.2 behavior (and main)
PreventFilteris checked only for a left-side member, by its name in the exact case after the query-name rewrite.PreventSortis checked by the typed path in the exact case. A caller can filter or sort by a prevented property in six ways:(Age + 0) > 30AgePreventFilterx => ((x.Age + 0) > 30), rows[Bob,Cid]FirstName == SecretSecretPreventFilterx => (x.FirstName == x.Secret)(secret, FirstName) @=* "s"SecretPreventFilterSecretFirstNamesecret == "s"SecretPreventFilter, query namehiddenx => (x.Secret == "s"), rows[Ann]fullName == "Ann Lee"age descAgePreventSort, query nameyears[Cid,Bob,Ann][Ann,Bob,Cid]A custom operation with
PreventFilteris also still applied on v1.14.2 and main.New behavior
Each path in the table above respects
PreventFilterandPreventSort. Filters and sorts that do not use a prevented property do not change.Example
WHERE p."Salary" + 0 > 50000. The caller sees which rows earn more than 50000.true == true(or removed withIgnoredClauseBehavior.Remove). Every row comes back, so the filter shows nothing aboutSalary.Security risk on v1.14.2
PreventFilterandPreventSortexist to stop a caller from querying a field. With any of the six bypasses, a caller can find the value of a hidden field one comparison at a time, for example(Salary + 0) > 50000, then> 75000, and so on. A sort bypass shows the order of the hidden values. Every app that relies on these settings to hide a field from API callers is exposed.Justification
The settings promise that a caller cannot filter or sort by the property. A check that covers only one syntax form does not keep that promise. This PR applies the settings to every path that reaches the property. The change breaks only callers that used a bypass. They now get the rows that the configuration permits.
Migration
No setting brings back the old behavior. If a caller needs to filter by a property, remove
PreventFilterfrom it.README
The Property Settings sections for filters and sorts now tell where
PreventFilterandPreventSortapply. ThePreventSortbullet also said "prevent filtering". It says "prevent sorting" now.Tests
These tests come back from main before #134.
Unit,
PropertyResolverTests:prevented_property_in_arithmetic_is_true_equals_true(back)prevented_property_in_arithmetic_is_still_filtered->prevented_property_in_arithmetic_removes_the_clauseprevented_property_on_the_right_side_of_arithmetic_is_true_equals_true(back)prevented_property_on_the_right_side_of_arithmetic_is_still_filtered->prevented_property_on_the_right_side_of_arithmetic_removes_the_clauseprevented_property_on_the_right_side_is_true_equals_true_when_replaced(back)prevented_property_on_the_right_side_is_still_compared->prevented_property_on_the_right_side_removes_the_clauseprevented_property_on_the_right_side_removes_the_clause_in_any_case(back)prevented_property_in_a_list_in_another_case_is_still_filtered->prevented_property_in_a_list_is_skipped_in_any_caseprevented_property_with_a_query_name_is_still_filtered_by_its_member_name_in_another_case->prevented_property_with_a_query_name_removes_the_clause_when_written_by_its_member_name_in_any_caseprevented_sort_property_with_a_query_name_still_sorts_by_its_member_name_in_another_case->prevented_sort_property_with_a_query_name_is_skipped_when_written_by_its_member_nameprevented_derived_property_is_still_filtered->prevented_derived_property_removes_the_clauseprevented_derived_property_in_a_list_is_still_filtered->prevented_derived_property_in_a_list_is_skippedprevented_custom_operation_is_still_applied->prevented_custom_operation_removes_the_clauseprevented_custom_operation_is_true_equals_true_when_replaced(back)prevented_derived_sort_property_still_sorts->prevented_derived_sort_property_is_skippedIntegration,
PropertyResolverTests(Postgres), all back:prevented_property_in_arithmetic_is_not_filteredprevented_property_on_the_right_side_is_not_comparedprevented_property_in_a_list_is_not_filtered_in_any_caseprevented_sort_property_with_a_query_name_is_not_sorted_when_written_by_its_member_nameprevented_custom_operation_is_not_filteredprevented_derived_property_is_not_filteredprevented_derived_sort_property_is_not_sorteddotnet test: 475 unit tests and 304 Postgres integration tests (Testcontainers) pass, 0 failures.Rebase on main
This branch is rebased on current main. This PR replaces the last call of the left-side helper
GetFilterPropertyInfo, so this PR deletes that helper. The main tests for a query name that matches another property path pass without a change.Interaction with #150 (item L)
#150 accepts a property path on the right side. After this PR and #150 are both merged, bring back 2 unit tests from main before #134 (
PropertyResolverTests):property_path_on_the_right_side_obeys_max_property_depthandprevented_property_path_on_the_right_side_removes_the_clause. Each test needs both changes. With both rebased branches merged locally and the 2 tests added, 476 unit tests pass.Interaction with #151 (item O)
#151 (item O) adds the same
ResolveWithoutDepthChecksplit and also edits the arithmeticSelect. If both merge, the second one gets a text conflict. The combined code is theResolveArithmeticPropertiesof main before #134, with theCanFiltercheck and the unknown-property check.Interaction with #154 (items J5 and J-bis)
#154 maps a query name in
PropertyResolver.Resolveand in arithmetic. It also changes the property-list check to find the setting by the query name first. If both merge, the second one gets text conflicts inResolve, in the property-list check, and in the tests. In the combinedResolve, the depth check uses the mapped path, andResolveWithoutDepthCheckmaps the query name too. Then the arithmeticCanFiltercheck of this PR also applies to a query name, for example(stars + 0) > 3withPreventFilter()onRating.