Browse Source

fix(ai): add separate PII check for sentry (#63511)

Co-authored-by: Zachary Collins <zachary.collins@sentry.io>
Tillman Elser 1 year ago
parent
commit
703d569be8

+ 7 - 4
src/sentry/api/endpoints/event_ai_suggested_fix.py

@@ -126,10 +126,10 @@ def get_openai_client() -> OpenAI:
     return openai_client
 
 
-def get_openai_policy(organization):
+def get_openai_policy(organization, user):
     """Uses a signal to determine what the policy for OpenAI should be."""
     results = openai_policy_check.send(
-        sender=EventAiSuggestedFixEndpoint, organization=organization
+        sender=EventAiSuggestedFixEndpoint, organization=organization, user=user
     )
     result = "allowed"
 
@@ -294,10 +294,10 @@ def reduce_stream(response):
 
 @region_silo_endpoint
 class EventAiSuggestedFixEndpoint(ProjectEndpoint):
+    owner = ApiOwner.ML_AI
     publish_status = {
         "GET": ApiPublishStatus.PRIVATE,
     }
-    owner = ApiOwner.ML_AI
     # go away
     private = True
     enforce_rate_limit = True
@@ -326,14 +326,17 @@ class EventAiSuggestedFixEndpoint(ProjectEndpoint):
             raise ResourceDoesNotExist
 
         # Check the OpenAI access policy
-        policy = get_openai_policy(request.organization)
+        policy = get_openai_policy(request.organization, request.user)
         policy_failure = None
         stream = request.GET.get("stream") == "yes"
+
         if policy == "subprocessor":
             policy_failure = "subprocessor"
         elif policy == "individual_consent":
             if request.GET.get("consent") != "yes":
                 policy_failure = "individual_consent"
+        elif policy == "pii_certification_required":
+            policy_failure = "pii_certification_required"
         elif policy == "allowed":
             pass
         else:

+ 28 - 14
static/app/components/events/aiSuggestedSolution/suggestion.tsx

@@ -1,4 +1,4 @@
-import {useCallback} from 'react';
+import {useCallback, useState} from 'react';
 import styled from '@emotion/styled';
 
 import {addSuccessMessage} from 'sentry/actionCreators/indicator';
@@ -66,23 +66,14 @@ function ErrorDescription({
   }
 
   if (restriction === 'individual_consent') {
-    const {isStaff} = ConfigStore.get('user');
-
-    const title = isStaff ? t('Confirm there is no PII') : t('We need your consent');
-    const description = isStaff
-      ? t(
-          'Before using this feature, please confirm that there is no personally identifiable information in this event.'
-        )
-      : t(
-          'By using this feature, you agree that OpenAI is a subprocessor and may process the data that you’ve chosen to submit. Sentry makes no guarantees as to the accuracy of the feature’s AI-generated recommendations.'
-        );
-
     const activeSuperUser = isActiveSuperuser();
     return (
       <EmptyMessage
         icon={<IconFlag size="xl" />}
-        title={title}
-        description={description}
+        title={t('We need your consent')}
+        description={t(
+          'By using this feature, you agree that OpenAI is a subprocessor and may process the data that you’ve chosen to submit. Sentry makes no guarantees as to the accuracy of the feature’s AI-generated recommendations.'
+        )}
         action={
           <ButtonBar gap={2}>
             <Button onClick={onHideSuggestion}>{t('Dismiss')}</Button>
@@ -112,6 +103,8 @@ export function Suggestion({onHideSuggestion, projectSlug, event}: Props) {
   const organization = useOrganization();
   const [suggestedSolutionLocalConfig, setSuggestedSolutionLocalConfig] =
     useOpenAISuggestionLocalStorage();
+  const [piiCertified, setPiiCertified] = useState(false);
+  const {isStaff} = ConfigStore.get('user');
 
   const {
     data,
@@ -125,10 +118,12 @@ export function Suggestion({onHideSuggestion, projectSlug, event}: Props) {
       {
         query: {
           consent: suggestedSolutionLocalConfig.individualConsent ? 'yes' : undefined,
+          pii_certified: isStaff ? (piiCertified ? 'yes' : 'no') : undefined,
         },
       },
     ],
     {
+      enabled: false,
       staleTime: Infinity,
       retry: false,
     }
@@ -138,6 +133,25 @@ export function Suggestion({onHideSuggestion, projectSlug, event}: Props) {
     addSuccessMessage('Thank you for your feedback!');
   }, []);
 
+  if (isStaff && !piiCertified) {
+    return (
+      <EmptyMessage
+        icon={<IconFlag size="xl" />}
+        title={t('PII Certification Required')}
+        description={t(
+          'Before using this feature, please confirm that there is no personally identifiable information in this event.'
+        )}
+        action={
+          <ButtonBar gap={2}>
+            <Button priority="primary" onClick={() => setPiiCertified(true)}>
+              {t('Certify No PII')}
+            </Button>
+          </ButtonBar>
+        }
+      />
+    );
+  }
+
   return (
     <Panel>
       <Header>

+ 6 - 1
tests/sentry/api/endpoints/test_event_ai_suggested_fix.py

@@ -81,7 +81,7 @@ def openai_policy():
 
 
 @django_db_all
-def test_consent(client, default_project, test_event, openai_policy):
+def test_consent(client, monkeypatch, default_user, default_project, test_event, openai_policy):
     path = reverse(
         "sentry-api-0-event-ai-fix-suggest",
         kwargs={
@@ -104,6 +104,11 @@ def test_consent(client, default_project, test_event, openai_policy):
     assert response.status_code == 403
     assert response.json() == {"restriction": "subprocessor"}
 
+    openai_policy["result"] = "pii_certification_required"
+    response = client.get(path)
+    assert response.status_code == 403
+    assert response.json() == {"restriction": "pii_certification_required"}
+
     openai_policy["result"] = "allowed"
     response = client.get(path)
     assert response.status_code == 200