djangoherokuerror-reportingsentrysensitive-data

How do I filter sensitive Django POST parameters out of Sentry error reports?


To quote the Django docs:

@sensitive_post_parameters('pass_word', 'credit_card_number')
def record_user_profile(request):
    UserProfile.create(user=request.user,
                       password=request.POST['pass_word'],
                       credit_card=request.POST['credit_card_number'],
                       name=request.POST['name'])

In the above example, the values for the pass_word and credit_card_number POST parameters will be hidden and replaced with stars (******) in the request’s representation inside the error reports, whereas the value of the name parameter will be disclosed.

To systematically hide all POST parameters of a request in error reports, do not provide any argument to the sensitive_post_parameters decorator:

@sensitive_post_parameters()
def my_view(request):
    ...

As a test, I added the following code to my Django 1.6 application:

views.py:

@sensitive_post_parameters('sensitive')
def sensitive(request):
    if request.method == 'POST':
        raise IntegrityError(unicode(timezone.now()))
    return render(request, 'sensitive-test.html',
          {'form': forms.SensitiveParamForm()})

forms.py:

class SensitiveParamForm(forms.Form):
    not_sensitive = forms.CharField(max_length=255)
    sensitive = forms.CharField(max_length=255)

When I submit this form via POST, I can see the values of both fields (including sensitive) clear as day in the Sentry report.

What am I doing wrong here? I'm using Django 1.6 and Raven 3.5.2.

Thanks in advance for your help!


Solution

  • Turns out that this stemmed from a bug in Django itself!

    If you haven't changed DEFAULT_EXCEPTION_REPORTER_FILTER in your settings file, you get the default filter of SafeExceptionReporterFilter.

    If you've used the sensitive_post_parameters decorator, this will result in your calling SafeExceptionReporterFilter's get_post_parameters method:

     def get_post_parameters(self, request):
            """
            Replaces the values of POST parameters marked as sensitive with
            stars (*********).
            """
            if request is None:
                return {}
            else:
                sensitive_post_parameters = getattr(request, 'sensitive_post_parameters', [])
                if self.is_active(request) and sensitive_post_parameters:
                    cleansed = request.POST.copy()
                    if sensitive_post_parameters == '__ALL__':
                        # Cleanse all parameters.
                        for k, v in cleansed.items():
                            cleansed[k] = CLEANSED_SUBSTITUTE
                        return cleansed
                    else:
                        # Cleanse only the specified parameters.
                        for param in sensitive_post_parameters:
                            if param in cleansed:
                                cleansed[param] = CLEANSED_SUBSTITUTE
                        return cleansed
                else:
                    return request.POST
    

    The problem with the above is that while it will correctly return a QuerySet with the sensitive POST parameters set to CLEANSED_SUBSTITUTE ('********************')...it won't in any way alter request.body.

    This is a problem when working with Raven/Sentry for Django, because it turns out that the get_data_from_request method of Raven's DjangoClient first attempts to get the request's POST parameters from request.body:

    def get_data_from_request(self, request):
    
      [snip]
    
        if request.method != 'GET':
            try:
                data = request.body
            except Exception:
                try:
                    data = request.raw_post_data
                except Exception:
                    # assume we had a partial read.
                    try:
                        data = request.POST or '<unavailable>'
                    except Exception:
                        data = '<unavailable>'
        else:
            data = None
    
     [snip]
    

    The fastest fix turned out to just involve subclassing DjangoClient and manually replacing its output with the cleansed QuerySet produced by SafeExceptionReporterFilter:

    from django.views.debug import SafeExceptionReporterFilter
    from raven.contrib.django.client import DjangoClient
    
    
    class SafeDjangoClient(DjangoClient):
    
      def get_data_from_request(self, request):
        request.POST = SafeExceptionReporterFilter().get_post_parameters(request)
        result = super(SafeDjangoClient, self).get_data_from_request(request)
        result['sentry.interfaces.Http']['data'] = request.POST
        return result