diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 288dc9a..1300e86 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -537,6 +537,10 @@ LOGGING = {
     }
 }
 
+# Default exception reporter filter class used in case none has been
+# specifically assigned to the HttpRequest instance.
+DEFAULT_EXCEPTION_REPORTER_FILTER = 'django.views.debug.SafeExceptionReporterFilter'
+
 ###########
 # TESTING #
 ###########
diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py
index 7d855d8..dc04e7c 100644
--- a/django/contrib/auth/admin.py
+++ b/django/contrib/auth/admin.py
@@ -12,6 +12,7 @@ from django.utils.html import escape
 from django.utils.decorators import method_decorator
 from django.utils.translation import ugettext, ugettext_lazy as _
 from django.views.decorators.csrf import csrf_protect
+from django.views.decorators.security import sensitive_post_parameters
 
 csrf_protect_m = method_decorator(csrf_protect)
 
@@ -78,6 +79,7 @@ class UserAdmin(admin.ModelAdmin):
             (r'^(\d+)/password/$', self.admin_site.admin_view(self.user_change_password))
         ) + super(UserAdmin, self).get_urls()
 
+    @sensitive_post_parameters
     @csrf_protect_m
     @transaction.commit_on_success
     def add_view(self, request, form_url='', extra_context=None):
@@ -102,6 +104,7 @@ class UserAdmin(admin.ModelAdmin):
         extra_context.update(defaults)
         return super(UserAdmin, self).add_view(request, form_url, extra_context)
 
+    @sensitive_post_parameters
     def user_change_password(self, request, id):
         if not self.has_change_permission(request):
             raise PermissionDenied
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index cfb2659..75c4a13 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -6,6 +6,7 @@ from django.http import HttpResponseRedirect, QueryDict
 from django.template.response import TemplateResponse
 from django.utils.http import base36_to_int
 from django.utils.translation import ugettext as _
+from django.views.decorators.security import sensitive_post_parameters
 from django.views.decorators.cache import never_cache
 from django.views.decorators.csrf import csrf_protect
 
@@ -17,7 +18,7 @@ from django.contrib.auth.models import User
 from django.contrib.auth.tokens import default_token_generator
 from django.contrib.sites.models import get_current_site
 
-
+@sensitive_post_parameters
 @csrf_protect
 @never_cache
 def login(request, template_name='registration/login.html',
@@ -175,6 +176,7 @@ def password_reset_done(request,
                             current_app=current_app)
 
 # Doesn't need csrf_protect since no-one can guess the URL
+@sensitive_post_parameters
 @never_cache
 def password_reset_confirm(request, uidb36=None, token=None,
                            template_name='registration/password_reset_confirm.html',
@@ -227,6 +229,7 @@ def password_reset_complete(request,
     return TemplateResponse(request, template_name, context,
                             current_app=current_app)
 
+@sensitive_post_parameters
 @csrf_protect
 @login_required
 def password_change(request,
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index d653860..7cae42e 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -206,7 +206,7 @@ class BaseHandler(object):
             exc_info=exc_info,
             extra={
                 'status_code': 500,
-                'request':request
+                'request': request
             }
         )
 
diff --git a/django/utils/log.py b/django/utils/log.py
index 93e38d1..738606b 100644
--- a/django/utils/log.py
+++ b/django/utils/log.py
@@ -1,6 +1,10 @@
 import logging
 import sys
+import traceback
+
+from django.conf import settings
 from django.core import mail
+from django.views.debug import ExceptionReporter, get_exception_reporter_filter
 
 # Make sure a NullHandler is available
 # This was added in Python 2.7/3.2
@@ -35,13 +39,9 @@ class AdminEmailHandler(logging.Handler):
     """An exception log handler that emails log entries to site admins.
 
     If the request is passed as the first argument to the log record,
-    request data will be provided in the
+    request data will be provided in the email report.
     """
     def emit(self, record):
-        import traceback
-        from django.conf import settings
-        from django.views.debug import ExceptionReporter
-
         try:
             request = record.request
             subject = '%s (%s IP): %s' % (
@@ -49,16 +49,16 @@ class AdminEmailHandler(logging.Handler):
                 (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'),
                 record.msg
             )
-            request_repr = repr(request)
+            filter = get_exception_reporter_filter(request)
+            request_repr = filter.get_request_repr()
         except:
             subject = '%s: %s' % (
                 record.levelname,
                 record.msg
             )
-
             request = None
-            request_repr = "Request repr() unavailable"
-
+            request_repr = "Request repr() unavailable."
+                
         if record.exc_info:
             exc_info = record.exc_info
             stack_trace = '\n'.join(traceback.format_exception(*record.exc_info))
diff --git a/django/views/debug.py b/django/views/debug.py
index 67f25b3..1ea7f52 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -3,6 +3,7 @@ import os
 import re
 import sys
 import types
+from pprint import pformat
 
 from django.conf import settings
 from django.http import HttpResponse, HttpResponseServerError, HttpResponseNotFound
@@ -15,6 +16,8 @@ from django.utils.encoding import smart_unicode, smart_str
 
 HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST|SIGNATURE')
 
+CLEANSED_SUBSTITUTE = u'********************'
+
 def linebreak_iter(template_source):
     yield 0
     p = template_source.find('\n')
@@ -31,7 +34,7 @@ def cleanse_setting(key, value):
     """
     try:
         if HIDDEN_SETTINGS.search(key):
-            cleansed = '********************'
+            cleansed = CLEANSED_SUBSTITUTE
         else:
             if isinstance(value, dict):
                 cleansed = dict((k, cleanse_setting(k, v)) for k,v in value.items())
@@ -59,12 +62,146 @@ def technical_500_response(request, exc_type, exc_value, tb):
     html = reporter.get_traceback_html()
     return HttpResponseServerError(html, mimetype='text/html')
 
+# Cache for the default exception reporter filter class.
+default_exception_reporter_filter = None
+
+def get_exception_reporter_filter(request):
+    global default_exception_reporter_filter
+    if default_exception_reporter_filter is None:
+        # Load the default filter for the first time and cache it.
+        modpath = settings.DEFAULT_EXCEPTION_REPORTER_FILTER
+        modname, classname = modpath.rsplit('.', 1)
+        try:
+            mod = import_module(modname)
+        except ImportError, e:
+            raise ImproperlyConfigured(
+            'Error importing default exception reporter filter %s: "%s"' % (modpath, e))
+        try:
+            default_exception_reporter_filter = getattr(mod, classname)
+        except AttributeError:
+            raise exceptions.ImproperlyConfigured('Default exception reporter filter module "%s" does not define a "%s" class' % (modname, classname))
+    if request:
+        filter = getattr(request, 'exception_reporter_filter', None)
+        if filter is None:
+            # The request hasn't been assigned a filter class yet, so assign it the default one.
+            request.exception_reporter_filter = default_exception_reporter_filter
+        return request.exception_reporter_filter(request)
+    else:
+        return default_exception_reporter_filter()
+
+class ExceptionReporterFilterBase(object):
+    """
+    Base for all exception reporter filter classes. All overridable hooks
+    contain lenient default behaviours.
+    """
+    def __init__(self, request=None):
+        self.request = request
+    
+    def show_request(self):
+        return self.request is not None
+    
+    def get_request_repr(self):
+        if self.show_request():
+            # Mimic HttpRequest's own __repr__() except filter the POST parameters.
+            return '<HttpRequest\nGET:%s,\nPOST:%s,\nCOOKIES:%s,\nMETA:%s>' % \
+                (pformat(self.request.GET), 
+                 pformat(self.get_filtered_post_dict()), 
+                 pformat(self.request.COOKIES), 
+                 pformat(self.request.META))
+        else:
+            return u''
+    
+    def show_source(self):
+        return True
+        
+    def get_filtered_post_dict(self):
+        if self.show_request():
+            return self.request.POST
+        else:
+            return {}
+    
+    def show_traceback_frames(self): 
+        return True
+    
+    def show_traceback_frame(self, tb_frame):
+        # Support for __traceback_hide__ which is used by a few libraries
+        # to hide internal frames.
+        return self.show_traceback_frames() and not tb_frame.f_locals.get('__traceback_hide__')
+    
+    def show_traceback_variables(self, tb_frame): 
+        return self.show_traceback_frame(tb_frame)
+    
+    def get_filtered_traceback_variables(self, tb_frame):
+        if self.show_traceback_variables():
+            return tb_frame.f_locals.items()
+        else:
+            return {}
+
+class SafeExceptionReporterFilter(ExceptionReporterFilterBase):
+    """
+    Use annotations made by the sensitive_post_parameters and
+    sensitive_variables decorators to filter out sensitive information.
+    """
+    def get_filtered_post_dict(self):
+        if not self.show_request():
+            return {}
+        else:
+            sensitive_post_parameters = getattr(self.request, 'sensitive_post_parameters', [])
+            if settings.DEBUG or not sensitive_post_parameters:
+                return self.request.POST
+            else:
+                cleansed = self.request.POST.copy()
+                if sensitive_post_parameters == '__EVERYTHING__':
+                    # Cleanse all parameters
+                    for k, v in cleansed.items():
+                        cleansed[k] = CLEANSED_SUBSTITUTE
+                    return cleansed
+                else:
+                    # Cleanse specified parameters
+                    for param in sensitive_post_parameters:
+                        if cleansed.has_key(param):
+                            cleansed[param] = CLEANSED_SUBSTITUTE
+                    return cleansed
+    
+    def get_filtered_traceback_variables(self, tb_frame):
+        if not self.show_traceback_variables(tb_frame):
+            return {}
+        else:
+            func_name = tb_frame.f_code.co_name
+            func = tb_frame.f_globals.get(func_name)
+            sensitive_variables = getattr(func, 'sensitive_variables', [])
+            cleansed = []
+            if settings.DEBUG or not sensitive_variables:
+                # Potentially clean only the request if it's one of the frame variables.
+                for name, value in tb_frame.f_locals.items():
+                    if value == self.request:
+                        value = self.get_request_repr()
+                    cleansed.append((name, value))
+                return cleansed
+            else:
+                if sensitive_variables == '__EVERYTHING__':
+                    # Cleanse all variables
+                    for name, value in tb_frame.f_locals.items():
+                        cleansed.append((name, CLEANSED_SUBSTITUTE))
+                    return cleansed
+                else:
+                    # Cleanse specified variables
+                    for name, value in tb_frame.f_locals.items():
+                        if name in sensitive_variables:
+                            value = CLEANSED_SUBSTITUTE
+                        elif value == self.request:
+                            # Clean the request's POST parameters.
+                            value = self.get_request_repr()
+                        cleansed.append((name, value))
+                    return cleansed
+    
 class ExceptionReporter(object):
     """
     A class to organize and coordinate reporting on exceptions.
     """
     def __init__(self, request, exc_type, exc_value, tb, is_email=False):
         self.request = request
+        self.exception_reporter_filter = get_exception_reporter_filter(request)
         self.exc_type = exc_type
         self.exc_value = exc_value
         self.tb = tb
@@ -123,7 +260,8 @@ class ExceptionReporter(object):
             'is_email': self.is_email,
             'unicode_hint': unicode_hint,
             'frames': frames,
-            'request': self.request,
+            'request': self.request if self.exception_reporter_filter.show_request() else None,
+            'filtered_POST': self.exception_reporter_filter.get_filtered_post_dict(),
             'settings': get_safe_settings(),
             'sys_executable': sys.executable,
             'sys_version_info': '%d.%d.%d' % sys.version_info[0:3],
@@ -142,7 +280,7 @@ class ExceptionReporter(object):
         if frames:
             c['lastframe'] = frames[-1]
         return t.render(c)
-
+    
     def get_template_exception_info(self):
         origin, (start, end) = self.exc_value.source
         template_source = origin.reload()
@@ -219,12 +357,12 @@ class ExceptionReporter(object):
         return lower_bound, pre_context, context_line, post_context
 
     def get_traceback_frames(self):
+        if not self.exception_reporter_filter.show_traceback_frames():
+            return []
         frames = []
         tb = self.tb
         while tb is not None:
-            # support for __traceback_hide__ which is used by a few libraries
-            # to hide internal frames.
-            if tb.tb_frame.f_locals.get('__traceback_hide__'):
+            if not self.exception_reporter_filter.show_traceback_frame(tb.tb_frame):
                 tb = tb.tb_next
                 continue
             filename = tb.tb_frame.f_code.co_filename
@@ -232,14 +370,17 @@ class ExceptionReporter(object):
             lineno = tb.tb_lineno - 1
             loader = tb.tb_frame.f_globals.get('__loader__')
             module_name = tb.tb_frame.f_globals.get('__name__')
-            pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file(filename, lineno, 7, loader, module_name)
+            if self.exception_reporter_filter.show_source():
+                pre_context_lineno, pre_context, context_line, post_context = self._get_lines_from_file(filename, lineno, 7, loader, module_name)
+            else:
+                pre_context_lineno, pre_context, context_line, post_context = '', '', '', ''
             if pre_context_lineno is not None:
                 frames.append({
                     'tb': tb,
                     'filename': filename,
                     'function': function,
                     'lineno': lineno + 1,
-                    'vars': tb.tb_frame.f_locals.items(),
+                    'vars': self.exception_reporter_filter.get_filtered_traceback_variables(tb.tb_frame),
                     'id': id(tb),
                     'pre_context': pre_context,
                     'context_line': context_line,
@@ -643,7 +784,7 @@ Exception Value: {{ exception_value|force_escape }}
   {% endif %}
 
   <h3 id="post-info">POST</h3>
-  {% if request.POST %}
+  {% if filtered_POST %}
     <table class="req">
       <thead>
         <tr>
@@ -652,7 +793,7 @@ Exception Value: {{ exception_value|force_escape }}
         </tr>
       </thead>
       <tbody>
-        {% for var in request.POST.items %}
+        {% for var in filtered_POST.items %}
           <tr>
             <td>{{ var.0 }}</td>
             <td class="code"><pre>{{ var.1|pprint }}</pre></td>
diff --git a/django/views/decorators/security.py b/django/views/decorators/security.py
new file mode 100644
index 0000000..8c92ec7
--- /dev/null
+++ b/django/views/decorators/security.py
@@ -0,0 +1,85 @@
+import functools
+
+
+def sensitive_variables(*variables):
+    """
+    Indicates which variables used in the decorated function are sensitive, so
+    that those variables can later be treated in a special way, for example
+    by hiding them when logging unhandled exceptions.
+    
+    Two forms are accepted:
+    
+    * with specified variable names:
+    
+        @sensitive_variables('password', 'credit_card')
+        def my_function()
+            password = 'foo'
+            credit_card = '5555 5555 5555 5555'
+            ...
+            
+    * without any specified variable names, in which case it is assumed that
+      all variables are considered sensitive:
+      
+        @sensitive_variables
+        def my_function()
+            ...
+    """
+    def decorator(func):
+        @functools.wraps(func)
+        def wrapper(*args, **kwargs):
+            if variables:
+                wrapper.sensitive_variables = variables
+            else:
+                wrapper.sensitive_variables = '__EVERYTHING__'
+            return func(*args, **kwargs)
+        return wrapper
+    if len(variables) == 1 and callable(variables[0]):
+        # This is of the form: func = sensitive_variables(func)
+        func = variables[0]
+        variables = []
+        return decorator(func)
+    else:
+        # This is of the form: func = sensitive_variables(...)(func)
+        return decorator
+
+
+def sensitive_post_parameters(*params):
+    """
+    Indicates which POST parameters used in the decorated view are sensitive,
+    so that those parameters can later be treated in a special way, for example
+    by hiding them when logging unhandled exceptions.
+    
+    Two forms are accepted:
+    
+    * with specified parameters:
+    
+        @sensitive_post_parameters('password', 'credit_card')
+        def my_view(request)
+            pw = request.POST['password']
+            cc = request.POST['credit_card']
+            ...
+            
+    * without any specified parameters, in which case it is assumed that
+      all parameters are considered sensitive:
+      
+        @sensitive_post_parameters
+        def my_view(request)
+            ...
+    """
+    def decorator(view):
+        @functools.wraps(view)
+        def wrapper(request, *args, **kwargs):
+            if params:
+                request.sensitive_post_parameters = params
+            else:
+                request.sensitive_post_parameters = '__EVERYTHING__'
+            return view(request, *args, **kwargs)
+        return wrapper
+    if len(params) == 1 and callable(params[0]):
+        # This is of the form: view = sensitive_post_parameters(view)
+        view = params[0]
+        params = []
+        return decorator(view)
+    else:
+        # This is of the form: view = sensitive_post_parameters(...)(view)
+        return decorator
diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py
index 6dd4bd4..44186b6 100644
--- a/tests/regressiontests/views/tests/debug.py
+++ b/tests/regressiontests/views/tests/debug.py
@@ -1,3 +1,4 @@
+from __future__ import with_statement
 import inspect
 import os
 import sys
@@ -8,8 +9,12 @@ from django.test import TestCase, RequestFactory
 from django.core.urlresolvers import reverse
 from django.template import TemplateSyntaxError
 from django.views.debug import ExceptionReporter
+from django.core.exceptions import ImproperlyConfigured
+from django.core import mail
 
 from regressiontests.views import BrokenException, except_args
+from regressiontests.views.views import (sensitive_view, non_sensitive_view,
+                                        paranoid_view)
 
 
 class DebugViewTests(TestCase):
@@ -143,3 +148,174 @@ class ExceptionReporterTests(TestCase):
         self.assertNotIn('<h2>Traceback ', html)
         self.assertIn('<h2>Request information</h2>', html)
         self.assertIn('<p>Request data not supplied</p>', html)
+
+
+class ExceptionReporterFilterTests(TestCase):
+    rf = RequestFactory()
+    breakfast_data = {'sausage-key': 'sausage-value',
+                      'baked-beans-key': 'baked-beans-value',
+                      'hash-brown-key': 'hash-brown-value',
+                      'bacon-key': 'bacon-value',}
+           
+    def verify_unsafe_response(self, view):
+        """
+        Asserts that potentially sensitive info are displayed in the response.
+        """
+        request = self.rf.post('/some_url/', self.breakfast_data)
+        response = view(request)
+        # All variables are shown.
+        self.assertContains(response, 'cooked_eggs', status_code=500)
+        self.assertContains(response, 'scrambled', status_code=500)
+        self.assertContains(response, 'sauce', status_code=500)
+        self.assertContains(response, 'worcestershire', status_code=500)
+        for k, v in self.breakfast_data.items():
+            # All POST parameters are shown.
+            self.assertContains(response, k, status_code=500)
+            self.assertContains(response, v, status_code=500)
+    
+    def verify_safe_response(self, view):
+        """
+        Asserts that certain sensitive info are not displayed in the response.
+        """
+        request = self.rf.post('/some_url/', self.breakfast_data)
+        response = view(request)
+        # Non-sensitive variable's name and value are shown.
+        self.assertContains(response, 'cooked_eggs', status_code=500)
+        self.assertContains(response, 'scrambled', status_code=500)
+        # Sensitive variable's name is shown but not its value.
+        self.assertContains(response, 'sauce', status_code=500)
+        self.assertNotContains(response, 'worcestershire', status_code=500)
+        for k, v in self.breakfast_data.items():
+            # All POST parameters' names are shown.
+            self.assertContains(response, k, status_code=500)
+        # Non-sensitive POST parameters' values are shown.
+        self.assertContains(response, 'baked-beans-value', status_code=500)
+        self.assertContains(response, 'hash-brown-value', status_code=500)
+        # Sensitive POST parameters' values are not shown.
+        self.assertNotContains(response, 'sausage-value', status_code=500)
+        self.assertNotContains(response, 'bacon-value', status_code=500)
+    
+    def verify_paranoid_response(self, view):
+        """
+        Asserts that no variables or POST parameters are displayed in the response.
+        """
+        request = self.rf.post('/some_url/', self.breakfast_data)
+        response = view(request)
+        # Show variable names but not their values.
+        self.assertContains(response, 'cooked_eggs', status_code=500)
+        self.assertNotContains(response, 'scrambled', status_code=500)
+        self.assertContains(response, 'sauce', status_code=500)
+        self.assertNotContains(response, 'worcestershire', status_code=500)
+        for k, v in self.breakfast_data.items():
+            # All POST parameters' names are shown.
+            self.assertContains(response, k, status_code=500)
+            # No POST parameters' values are shown.
+            self.assertNotContains(response, v, status_code=500)
+        
+    def verify_unsafe_email(self, view):
+        """
+        Asserts that potentially sensitive info are displayed in the email report.
+        """
+        with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
+            mail.outbox = [] # Empty outbox
+            request = self.rf.post('/some_url/', self.breakfast_data)
+            response = view(request) 
+            self.assertEquals(len(mail.outbox), 1)
+            email = mail.outbox[0]
+            # Frames vars are never shown in plain text email reports.
+            self.assertNotIn('cooked_eggs', email.body)
+            self.assertNotIn('scrambled', email.body)
+            self.assertNotIn('sauce', email.body)
+            self.assertNotIn('worcestershire', email.body)
+            for k, v in self.breakfast_data.items():
+                # All POST parameters are shown.
+                self.assertIn(k, email.body)
+                self.assertIn(v, email.body)
+        
+    def verify_safe_email(self, view):
+        """
+        Asserts that certain sensitive info are not displayed in the email report.
+        """
+        with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
+            mail.outbox = [] # Empty outbox
+            request = self.rf.post('/some_url/', self.breakfast_data)
+            response = view(request)
+            self.assertEquals(len(mail.outbox), 1)
+            email = mail.outbox[0]
+            # Frames vars are never shown in plain text email reports.
+            self.assertNotIn('cooked_eggs', email.body)
+            self.assertNotIn('scrambled', email.body)
+            self.assertNotIn('sauce', email.body)
+            self.assertNotIn('worcestershire', email.body)
+            for k, v in self.breakfast_data.items():
+                # All POST parameters' names are shown.
+                self.assertIn(k, email.body)
+            # Non-sensitive POST parameters' values are shown.
+            self.assertIn('baked-beans-value', email.body)
+            self.assertIn('hash-brown-value', email.body)
+            # Sensitive POST parameters' values are not shown.
+            self.assertNotIn('sausage-value', email.body)
+            self.assertNotIn('bacon-value', email.body)
+    
+    def verify_paranoid_email(self, view):
+        """
+        Asserts that no variables or POST parameters are displayed in the email report.
+        """
+        with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
+            mail.outbox = [] # Empty outbox
+            request = self.rf.post('/some_url/', self.breakfast_data)
+            response = view(request)
+            self.assertEquals(len(mail.outbox), 1)
+            email = mail.outbox[0]
+            # Frames vars are never shown in plain text email reports.
+            self.assertNotIn('cooked_eggs', email.body)
+            self.assertNotIn('scrambled', email.body)
+            self.assertNotIn('sauce', email.body)
+            self.assertNotIn('worcestershire', email.body)
+            for k, v in self.breakfast_data.items():
+                # All POST parameters' names are shown.
+                self.assertIn(k, email.body)
+                # No POST parameters' values are shown.
+                self.assertNotIn(v, email.body)
+            
+    def test_non_sensitive_request(self):
+        """
+        Ensure that everything (request info and frame variables) can bee seen
+        in the default error reports for non-sensitive requests.
+        Refs #14614.
+        """ 
+        with self.settings(DEBUG=True):
+            self.verify_unsafe_response(non_sensitive_view)
+            self.verify_unsafe_email(non_sensitive_view)
+                
+        with self.settings(DEBUG=False):
+            self.verify_unsafe_response(non_sensitive_view)
+            self.verify_unsafe_email(non_sensitive_view)
+        
+    def test_sensitive_request(self):
+        """
+        Ensure that sensitive POST parameters and frame variables cannot be
+        seen in the default error reports for sensitive requests.
+        Refs #14614.
+        """
+        with self.settings(DEBUG=True):
+            self.verify_unsafe_response(sensitive_view)
+            self.verify_unsafe_email(sensitive_view)
+            
+        with self.settings(DEBUG=False):
+            self.verify_safe_response(sensitive_view)
+            self.verify_safe_email(sensitive_view)
+    
+    def test_paranoid_request(self):
+        """
+        Ensure that no POST parameters and frame variables can be seen in the
+        default error reports for "paranoid" requests.
+        Refs #14614.
+        """
+        with self.settings(DEBUG=True):
+            self.verify_unsafe_response(paranoid_view)
+            self.verify_unsafe_email(paranoid_view)
+            
+        with self.settings(DEBUG=False):
+            self.verify_paranoid_response(paranoid_view)
+            self.verify_paranoid_email(paranoid_view)
diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py
index 11d289f..7f274c3 100644
--- a/tests/regressiontests/views/views.py
+++ b/tests/regressiontests/views/views.py
@@ -6,6 +6,9 @@ from django.core.urlresolvers import get_resolver
 from django.shortcuts import render_to_response, render
 from django.template import Context, RequestContext, TemplateDoesNotExist
 from django.views.debug import technical_500_response
+from django.views.decorators.security import (sensitive_post_parameters,
+                                              sensitive_variables)
+from django.utils.log import getLogger
 
 from regressiontests.views import BrokenException, except_args
 
@@ -128,3 +131,56 @@ def raises_template_does_not_exist(request):
         return render_to_response('i_dont_exist.html')
     except TemplateDoesNotExist:
         return technical_500_response(request, *sys.exc_info())
+
+def send_log(request, exc_info):
+    logger = getLogger('django.request')
+    logger.error('Internal Server Error: %s' % request.path,
+        exc_info=exc_info,
+        extra={
+            'status_code': 500,
+            'request': request
+        }
+    )
+
+def non_sensitive_view(request):
+    # Do not just use plain strings for the variables' values in the code
+    # so that the tests don't return false positives when the function's source
+    # is displayed in the exception report. 
+    cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+    sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
+    try:
+        raise Exception
+    except Exception:
+        exc_info = sys.exc_info()
+        send_log(request, exc_info)
+        return technical_500_response(request, *exc_info)
+    
+@sensitive_variables('sauce')
+@sensitive_post_parameters('bacon-key', 'sausage-key')
+def sensitive_view(request):
+    # Do not just use plain strings for the variables' values in the code
+    # so that the tests don't return false positives when the function's source
+    # is displayed in the exception report.
+    cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+    sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
+    try:
+        raise Exception
+    except Exception:
+        exc_info = sys.exc_info()
+        send_log(request, exc_info)
+        return technical_500_response(request, *exc_info)
+
+@sensitive_variables
+@sensitive_post_parameters
+def paranoid_view(request):
+    # Do not just use plain strings for the variables' values in the code
+    # so that the tests don't return false positives when the function's source
+    # is displayed in the exception report.
+    cooked_eggs = ''.join(['s', 'c', 'r', 'a', 'm', 'b', 'l', 'e', 'd'])
+    sauce = ''.join(['w', 'o', 'r', 'c', 'e', 's', 't', 'e', 'r', 's', 'h', 'i', 'r', 'e'])
+    try:
+        raise Exception
+    except Exception:
+        exc_info = sys.exc_info()
+        send_log(request, exc_info)
+        return technical_500_response(request, *exc_info)
