graphql: separate prefix authorization from user filters - #3304
Draft
GregorShear wants to merge 3 commits into
Draft
graphql: separate prefix authorization from user filters#3304GregorShear wants to merge 3 commits into
GregorShear wants to merge 3 commits into
Conversation
GregorShear
force-pushed
the
greg/prefix-filter-refactor
branch
from
August 3, 2026 23:44
2d8729d to
565502a
Compare
authorized_prefixes now answers only the authorization question: min_capability becomes required_capabilities (a CapabilitySet whose bits must all be held, not an ordered threshold), the parent-pruning step is extracted as prune_covered, and the startsWith overlap moves out of the function into PrefixFilter::narrow_to_overlap — the counterpart of narrow_to_exact_set. Both filter modes are approximate narrowing post-passes over the authorized set, chained by filtered_authorized_prefixes, with exactness enforced by each resolver's SQL. Behavior-preserving: overlap filtering commutes with parent-pruning, since any prefix overlapping the filter has all of its ancestors overlapping it too.
GregorShear
force-pushed
the
greg/prefix-filter-refactor
branch
from
August 3, 2026 23:46
565502a to
f9a9d90
Compare
Filters no longer narrow the caller's authorized prefix set in memory. `authorized_prefixes` is built from grants, and `filtered_authorized_prefixes` returns the decomposed `startsWith`/`in` parts for the resolver to bind alongside that set, where SQL ANDs an unconditional `^@ ANY($authorized)` scope check against the filter's own clause. Authorization is never a function of caller input, so a bug in filter handling can only return too few rows, never widen visibility. `PrefixFilter::narrow_to_overlap` and `narrow_to_exact_set` are removed along with their unit tests. The four per-resolver `MAX_PREFIXES` constants are replaced by a single shared bound of 100 in `authorized_prefixes`: without a narrowing pass the cap applies to the grant-derived set whether or not a filter is present, so it has to accommodate callers who legitimately hold many prefixes. The bound exists because that set is bound into SQL as a `text[]`, and it matches the 100-entry ceiling already enforced on `in`. Error messages now name the limit. The alert-configs regression test is inverted to match: a caller over the cap is refused both with and without a filter.
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.
Prefix-scoped GraphQL list queries resolved the caller's authorized prefixes and applied the caller's
PrefixFilterin the same pass, so the set handed to SQL was partly a function of caller input. This separates the two.Authorization from grants alone
authorized_prefixesnow answers only the authorization question. It takesrequired_capabilities— aCapabilitySetwhose bits must all be held, replacing the orderedmin_capabilitythreshold — and no longer accepts a prefix filter. Parent-pruning is extracted asprune_covered.filtered_authorized_prefixesreturns the grant-derived set alongside the filter's decomposedstartsWith/inparts. The two stay separate all the way into SQL, where each resolver ANDs an unconditional^@ ANY($authorized)scope check against the filter's own clause. A bug in filter handling can therefore only return too few rows, never widen visibility.PrefixFilter::narrow_to_overlapandnarrow_to_exact_setare removed.One shared prefix cap
The four per-resolver
MAX_PREFIXESconstants (alert_configs,invite_links,service_accounts,storage_mappings) collapse into a singleMAX_PREFIXES: usize = 100inauthorized_prefixes. The bound exists because the authorized set is bound into SQL as atext[], and 100 matches the ceiling already enforced onin.Without an in-memory narrowing pass, the cap applies to the grant-derived set whether or not a filter is present, so the alert-configs regression test now asserts that a caller over the cap is refused both with and without a filter. Error messages name the limit.
Fully attenuated prefixes
UserGrant::reachable_prefixesrecorded every reachable node, including ones where delegation attenuation removed all capability bits along the path. Those destinations landed in the prefix closure with an empty capability set. They are now omitted, with a regression test covering an Editor grant that delegates across a TeamAdmin role edge — Editor carries Delegate so the edge is walked, but the two bundles share no bits, so nothing survives the path.Base of #3292.