feat(authz): support 'unscoped' wildcard sentinel in /v1/authz/search#303
Open
smoreinis wants to merge 4 commits into
Open
feat(authz): support 'unscoped' wildcard sentinel in /v1/authz/search#303smoreinis wants to merge 4 commits into
smoreinis wants to merge 4 commits into
Conversation
- Add fail-closed test: response missing both 'unscoped' and 'items' raises KeyError - Use an unambiguous truthy-non-boolean value (1) in the strict-sentinel test
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.
Summary
Lets an auth provider answer
POST /v1/authz/searchwith the wildcard sentinel{ "unscoped": true }to mean all resources of this type, which the Agentex authorization proxy maps onto the existing unscoped path (None→ noid IN (...)filter) instead of an enumerated id list.adapter_agentex_authz_proxy.py): recognizeunscoped: trueand returnNone; otherwise passitemsthrough unchanged.port.py): widenlist_resourcesreturn type toIterable[str] | None.auth_provider_contract.md): formalize theunscopedsentinel in thesearchresponse schema, the inclusion-filter note, and the endpoint summary.Motivation
A provider that grants broad access (e.g. a single-account "allow everything" authorization model) previously had to enumerate every accessible id, because
items: []hides everything and there was no "all resources" signal in the contract. Enumeration couples the provider to Agentex's storage and scales poorly on large accounts. The sentinel lets such a provider answersearchstatelessly.Design notes
items: null: a provider must deliberately opt into "everything", and a malformed response missing both fields raises rather than silently failing open.AuthorizationService.list_resourcesalready returnsIterable[str] | None, and every consumer (DAuthorizedResourceIds, the list routes, the Postgres list filter) already treatsNoneas "no filter" — the path taken when authorization is disabled. The sentinel reuses that exact, already-tested path.itemsarray behave exactly as before.Test plan
tests/unit/adapters/authorization/test_adapter_agentex_authz_proxy.py):{ "items": ["a","b"] }→["a","b"]{ "unscoped": true }→None{ "unscoped": true, "items": ["x"] }→None(unscoped wins){ "items": [] }→[](sentinel not triggered; still hides everything)Greptile Summary
Adds an
unscoped: truewildcard sentinel toPOST /v1/authz/searchso auth providers can signal "all resources of this type" without enumerating IDs, mapping it onto the existingNone(no-filter) path that authorization-bypass already uses.adapter_agentex_authz_proxy.py): a single strictis Trueidentity check intercepts the sentinel before falling through toresponse["items"]; malformed responses missing both fields still raiseKeyError(fail-closed), and the comment explains why.getis intentionally avoided in the normal path.port.py):list_resourcesreturn type widened toIterable[str] | None; all existing consumers (authorization_service, list routes, Postgres filter) already treatNoneas "no filter" — no downstream changes required.KeyErrorinvariant.Confidence Score: 5/5
Safe to merge — the change is additive and backward compatible, with strict identity checking to prevent unintentional sentinel activation and fail-closed behavior preserved for malformed responses.
The sentinel check uses
is True(not a truthy test), so only an explicit JSON booleantruetriggers the unscoped path. The fallback toresponse["items"](direct key access) preserves the existing fail-closed contract. All downstream consumers already handleNoneas "no filter". The test suite covers every branch including the malformed-responseKeyErrorinvariant. No changes to consumers, migrations, or data models are needed.No files require special attention.
Important Files Changed
is Truesentinel check forunscopedbefore falling through toresponse["items"]; fail-closed behavior (KeyError on malformed response) is preserved and documented.list_resourcesreturn type fromIterable[str]to `Iterable[str]unscoped: true, unscoped wins over items, empty items, truthy-but-not-boolean unscoped, and fail-closed KeyError on malformed response.unscopedsentinel in the response schema, the inclusion-filter note, and the endpoint summary table; accurate and consistent with the implementation.__init__.pyto make the new test directory a Python package.Sequence Diagram
sequenceDiagram participant Client participant AgentexProxy as AgentexAuthorizationProxy participant AuthProvider as Auth Provider (external) participant Consumer as list_resources consumer Client->>AgentexProxy: list_resources(principal, resource_type) AgentexProxy->>AuthProvider: POST /v1/authz/search AuthProvider-->>AgentexProxy: "200 {items: [a, b]}" AgentexProxy-->>Consumer: [a, b] (inclusion filter) Client->>AgentexProxy: list_resources(principal, resource_type) AgentexProxy->>AuthProvider: POST /v1/authz/search AuthProvider-->>AgentexProxy: "200 {unscoped: true}" Note over AgentexProxy: response.get(unscoped) is True → return None AgentexProxy-->>Consumer: None (no id filter — all resources) Client->>AgentexProxy: list_resources(principal, resource_type) AgentexProxy->>AuthProvider: POST /v1/authz/search AuthProvider-->>AgentexProxy: "200 {unexpected_key: val}" Note over AgentexProxy: response[items] raises KeyError (fail-closed) AgentexProxy--xConsumer: KeyErrorReviews (2): Last reviewed commit: "test(authz): address greptile review fin..." | Re-trigger Greptile