feat(sentry-apps): Validate org membership before sending webhook disable emails#114799
Conversation
…able emails Add RPC method get_notification_emails_for_sentry_app to app_service that validates the creator email against UserEmail (verified, active, org member) before sending circuit breaker notifications. Falls back to org owner email when the creator is no longer a valid recipient. Previously, webhook disable emails were sent to sentry_app.creator_label without verifying the creator was still an org member — a security risk if the creator had since left the organization. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Stop mocking the entire NotificationService class. Instead, patch only notify_async and enable the notification-platform feature flag with rollout options, matching the pattern in notification platform tests. Also removes _get_notification_recipients mocks so integration tests exercise the real RPC recipient resolution path. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
The caller already has the sentry_app with owner_id, so pass organization_id directly instead of sentry_app_id + re-querying. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
Rename to _is_valid_creator_email and return bool instead of list[str]. The caller now wraps the email in a list only when validation passes. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
_get_org_owner_emails now returns the full list so the RPC layer doesn't impose a limit. The webhook caller truncates to 1 recipient to avoid spamming when there are many owners. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
The limit-to-1 decision belongs in the notification sender, not the recipient resolver. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
| if not recipient_emails: | ||
| logger.info( | ||
| "sentry_app.webhook.circuit_breaker.no_recipients", | ||
| extra={"slug": sentry_app.slug, "owner_id": sentry_app.owner_id}, | ||
| ) |
There was a problem hiding this comment.
I missed this in my earlier review, but we need to split this into its own PR. RPC service methods need to be deployed before usage is rolled out unless this is behind a FF
Revert webhooks.py and test changes — RPC methods must be deployed before callers are rolled out. This PR now only contains the RPC service definition; webhook usage will follow in a separate PR. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
_get_org_owner_emails was reading User.email directly, which is not guaranteed to be verified. Now joins through UserEmail with is_verified=True to match the same verification standard used by _is_valid_creator_email. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
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 5a4e45e. Configure here.
| user__is_active=True, | ||
| user__emails__is_verified=True, | ||
| ).values_list("user__emails__email", flat=True) | ||
| ) |
There was a problem hiding this comment.
Query returns all verified emails per owner, causing duplicates
Medium Severity
_get_org_owner_emails joins through user__emails and uses values_list("user__emails__email", flat=True), which produces one row per verified UserEmail per org owner. If an owner has multiple verified email addresses, all of them are returned. This contradicts the PR description which says the function returns "the first email from an org owner," and could result in duplicate notifications being sent to the same person at different addresses.
Reviewed by Cursor Bugbot for commit 5a4e45e. Configure here.
There was a problem hiding this comment.
ill change the pr description 💀
…pp RPC Test the RPC implementation directly covering: valid creator, non-member fallback, unverified email fallback, username without @, no valid recipients, inactive creator, and unverified owner exclusion. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>


Summary
Context
In #114115 , @GabeVillalobos raised a good point where it's possible the creator of an app may not be apart of an organization (removed, deleted etc.) so we should validate org membership. Since webhooks are primarily sent from cell/region side, we need RPCs to do the validation which is all on the
User/UserEmail/OrganizationMemberMappingmodels which are all in control. So to reduce the RPC calls we're consolidating to 1 specific RPC.Changes