feat: flags and rpc for frontend code search tool#116098
Conversation
… request Add GetOrgFeatureFlagsTest covering prefix stripping, inactive exclusion, and sort order. Mock features.all to a controlled 2-flag subset so each test runs against a deterministic set rather than all 100+ api-exposed features. Add CollectUserOrgContextTest coverage for the new org_features key across the member and non-member return paths. Patch _get_org_feature_flags at the context test boundary to keep those tests focused on dict shape rather than flag logic. Add TestSeerAgentClient tests verifying that enable_frontend_code_search is included in the chat request payload when the seer-agent-source-code-search flag is active, and omitted when it is not. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
…ookup Move OrganizationFeature import to module top per project conventions. Use a set for features_to_check so that discard() replaces list.remove(), eliminating the ValueError risk when batch_has returns a key that was not in the original input (e.g. with a custom entity handler), and reducing removal from O(n) to O(1) per item. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
…valuation Move feature flag evaluation out of the chat request path and into a dedicated Seer RPC endpoint (get_organization_features). Previously all api-exposed org feature flags were evaluated on every chat request, contributing ~600ms p90 latency. Seer can now fetch flags on demand via the existing org-scoped RPC surface when it needs to validate what the user can see in the frontend. Remove _get_org_feature_flags and its call from collect_user_org_context. Add get_organization_features to seer_rpc.py and register it in the public_org_seer_method_registry. Update tests accordingly. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
…ests Replace two standalone tests for enable_frontend_code_search with inline assertions in existing tests. The inactive-flag case is now checked in test_start_run_basic; the active-flag case is checked in test_start_run_with_categories. Also restore test_collect_context_with_non_member_returns_default to the original exact-dict assertion. Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
The method was only registered on the org-scoped public RPC surface but missing from seer_method_registry, so service-authenticated Seer calls to the internal endpoint could not invoke it. Add it alongside its peer methods and add a test confirming it is reachable via the signed internal RPC path.
… flag evaluation The org serializer passes actor=user when computing organization.features, so evaluating without an actor can diverge from what the frontend shows for user-scoped Flagpole rules. Add an optional user_id parameter and resolve it via user_service (cross-silo safe) to use as actor, matching the serializer. Falls back to no actor when user_id is omitted or the user doesn't exist.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 86b9c95. Configure here.
| if features.has(name, organization, actor=actor, skip_entity=True): | ||
| feature_set.add(name[len(_ORGANIZATION_SCOPE_PREFIX) :]) | ||
|
|
||
| return {"features": list(sorted(feature_set))} |
There was a problem hiding this comment.
Duplicated feature-checking logic from organization serializer
Low Severity
The new get_organization_features function duplicates the batch-then-fallback feature-checking logic already present in src/sentry/api/serializers/models/organization.py (lines 462–497), including the _ORGANIZATION_SCOPE_PREFIX constant, the features.all() + batch_has + has(skip_entity=True) pattern, and the prefix-stripping step. Extracting this shared logic into a common utility would reduce the maintenance burden and the risk of the two implementations diverging.
Reviewed by Cursor Bugbot for commit 86b9c95. Configure here.
| @@ -87,6 +88,7 @@ | |||
| # Common to Seer features | |||
| "get_organization_project_ids": map_org_id_param(get_organization_project_ids), | |||
There was a problem hiding this comment.
Caller-controlled user_id in get_organization_features lets org members evaluate api-exposed feature flags as arbitrary users
OrganizationSeerRpcEndpoint._dispatch_to_local_method only strips organization_id from the caller-supplied arguments dict before forwarding to get_organization_features, so an authenticated org member can include an arbitrary user_id in the request body. That id is loaded via user_service.get_user and used as the actor for features.batch_has/features.has, allowing the caller to learn which api_expose_only org feature flags evaluate true for any user id (e.g. flags gated on staff/superuser/per-user rollout). The dispatch should ignore caller-supplied user_id and pass request.user.id instead.
Evidence
get_organization_features(*, org_id, user_id=None)inseer_rpc.pyresolvesactor = user_service.get_user(user_id=user_id)and uses it for everyfeatures.batch_has/features.hascall.OrganizationSeerRpcEndpoint._dispatch_to_local_method(organization_seer_rpc.py) only doesarguments.pop("organization_id", None)and thenmethod(**arguments)withorganization_idreinjected;user_idfrom the request body is forwarded unchanged.- The registered wrapper
map_org_id_param(get_organization_features)(utils.py) renamesorganization_id→org_idand forwards all other kwargs (return func(**kwargs)), so the attacker-controlleduser_idreaches the sink. - Request args are taken from
request.data.get("args", {})inpost(), fully attacker-controlled JSON; the endpoint requires only org-member scope (SeerRpcPermissionorg:read/write/admin) plus theorganizations:seer-public-rpcfeature. - Disclosed surface is limited to
api_expose_only=Trueorg-prefixed flags, but per-user gating (staff/superuser/rollout) means the result still leaks info about arbitrary users.
Identified by Warden security-review · 4HU-EW8


Uh oh!
There was an error while loading. Please reload this page.