diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index cfb2659..72ca81c 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.debug import sensitive
 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
 @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
 @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
 @csrf_protect
 @login_required
 def password_change(request,
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index f216886..6ccf5b2 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..3899b8c 100644
--- a/django/utils/log.py
+++ b/django/utils/log.py
@@ -1,6 +1,8 @@
 import logging
 import sys
+
 from django.core import mail
+from django.conf import settings
 
 # Make sure a NullHandler is available
 # This was added in Python 2.7/3.2
@@ -42,23 +44,33 @@ class AdminEmailHandler(logging.Handler):
         from django.conf import settings
         from django.views.debug import ExceptionReporter
 
-        try:
-            request = record.request
-            subject = '%s (%s IP): %s' % (
-                record.levelname,
-                (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'),
-                record.msg
-            )
-            request_repr = repr(request)
-        except:
+        if (not settings.DEBUG and
+            hasattr(record, 'request') and
+            hasattr(record.request, 'is_sensitive') and
+            record.request.is_sensitive):
             subject = '%s: %s' % (
-                record.levelname,
-                record.msg
-            )
-
+                    record.levelname,
+                    record.msg
+                )
             request = None
-            request_repr = "Request repr() unavailable"
-
+            request_repr = "Request is sensitive, so it is not provided in this report."
+        else:
+            try:
+                request = record.request
+                subject = '%s (%s IP): %s' % (
+                    record.levelname,
+                    (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS and 'internal' or 'EXTERNAL'),
+                    record.msg
+                )
+                request_repr = repr(request)
+            except:
+                subject = '%s: %s' % (
+                    record.levelname,
+                    record.msg
+                )
+                request = None
+                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..11bcb07 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -3,6 +3,7 @@ import os
 import re
 import sys
 import types
+import functools
 
 from django.conf import settings
 from django.http import HttpResponse, HttpResponseServerError, HttpResponseNotFound
@@ -15,6 +16,13 @@ from django.utils.encoding import smart_unicode, smart_str
 
 HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD|PROFANITIES_LIST|SIGNATURE')
 
+def sensitive(view):
+    @functools.wraps(view)
+    def wrapper(request, *args, **kwargs):
+        request.is_sensitive = True
+        return view(request, *args, **kwargs)
+    return wrapper
+
 def linebreak_iter(template_source):
     yield 0
     p = template_source.find('\n')
@@ -123,7 +131,7 @@ class ExceptionReporter(object):
             'is_email': self.is_email,
             'unicode_hint': unicode_hint,
             'frames': frames,
-            'request': self.request,
+            'request': self.request if self.display_sensitive_info() else None,
             'settings': get_safe_settings(),
             'sys_executable': sys.executable,
             'sys_version_info': '%d.%d.%d' % sys.version_info[0:3],
@@ -143,6 +151,15 @@ class ExceptionReporter(object):
             c['lastframe'] = frames[-1]
         return t.render(c)
 
+    def display_sensitive_info(self):
+        if (not settings.DEBUG and
+            self.request is not None and
+            hasattr(self.request, 'is_sensitive') and
+            self.request.is_sensitive):
+            return False
+        else:
+            return True
+    
     def get_template_exception_info(self):
         origin, (start, end) = self.exc_value.source
         template_source = origin.reload()
@@ -239,7 +256,7 @@ class ExceptionReporter(object):
                     'filename': filename,
                     'function': function,
                     'lineno': lineno + 1,
-                    'vars': tb.tb_frame.f_locals.items(),
+                    'vars': tb.tb_frame.f_locals.items() if self.display_sensitive_info() else [],
                     'id': id(tb),
                     'pre_context': pre_context,
                     'context_line': context_line,
diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py
index d778311..3ea5d90 100644
--- a/tests/regressiontests/views/tests/debug.py
+++ b/tests/regressiontests/views/tests/debug.py
@@ -1,14 +1,18 @@
+from __future__ import with_statement
 import inspect
 import sys
 
 from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
 from django.core.files.uploadedfile import SimpleUploadedFile
 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 import mail
 
 from regressiontests.views import BrokenException, except_args
+from regressiontests.views.views import sensitive_view, non_sensitive_view
 
 
 class DebugViewTests(TestCase):
@@ -61,6 +65,10 @@ class DebugViewTests(TestCase):
 
 class ExceptionReporterTests(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 test_request_and_exception(self):
         "A simple exception report can be generated"
@@ -81,6 +89,96 @@ class ExceptionReporterTests(TestCase):
         self.assertIn('<h2>Request information</h2>', html)
         self.assertNotIn('<p>Request data not supplied</p>', html)
 
+    def verify_unsafe_response(self, view):
+        """
+        Asserts that potentially sensitive info are displayed in the response.
+        """
+        request = self.rf.get('/some_url/', dict(cooked_eggs='scrambled', **self.breakfast_data))
+        response = view(request)
+        self.assertContains(response, 'cooked_eggs', status_code=500)
+        self.assertContains(response, 'scrambled', status_code=500)
+        for k, v in self.breakfast_data.items():
+            self.assertContains(response, k, status_code=500)
+            self.assertContains(response, v, status_code=500)
+    
+    def verify_unsafe_email(self, view):
+        """
+        Asserts that potentially sensitive info are displayed in the error email.
+        """
+        with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
+            mail.outbox = [] # Empty outbox
+            request = self.rf.get('/some_url/', dict(cooked_eggs='scrambled', **self.breakfast_data))
+            response = view(request)
+            
+            # TODO: Figure out why 2 emails get sent: len(outbox)==2
+            # Frames vars are never shown in plain text error emails
+            email = mail.outbox[0]
+            self.assertIn('cooked_eggs', email.body)
+            self.assertIn('scrambled', email.body)
+            for k, v in self.breakfast_data.items():
+                self.assertIn(k, email.body)
+                self.assertIn(v, email.body)
+       
+    def verify_safe_response(self, view):
+        """
+        Asserts that potentially sensitive info are not displayed in the response.
+        """
+        request = self.rf.get('/some_url/', dict(cooked_eggs='scrambled', **self.breakfast_data))
+        response = view(request)
+        self.assertContains(response, 'cooked_eggs', status_code=500)
+        self.assertNotContains(response, 'scrambled', status_code=500)
+        for k, v in self.breakfast_data.items():
+            self.assertNotContains(response, k, status_code=500)
+            self.assertNotContains(response, v, status_code=500)
+    
+    def verify_safe_email(self, view):
+        """
+        Asserts that potentially sensitive info are not displayed in the error email.
+        """
+        with self.settings(ADMINS=(('Admin', 'admin@fattie-breakie.com'),)):
+            mail.outbox = [] # Empty outbox
+            request = self.rf.get('/some_url/', dict(cooked_eggs='scrambled', **self.breakfast_data))
+            response = view(request)
+            
+            # TODO: Figure out why 2 emails get sent: len(outbox)==2
+            # Frames vars are never shown in plain text error emails
+            email = mail.outbox[0]
+            self.assertIn('cooked_eggs', email.body)
+            self.assertNotIn('scrambled', email.body)
+            for k, v in self.breakfast_data.items():
+                self.assertNotIn(k, email.body)
+                self.assertNotIn(v, email.body)
+                
+    def test_non_sensitive_request(self):
+        """
+        Ensure that everything (request info and frame vars) can bee seen
+        in the default exception reporters for views that are not marked as
+        sensitive.
+        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 the request info and frame vars cannot be seen
+        in the default exception reporters for views that are marked as
+        sensitive.
+        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_no_request(self):
         "An exception report can be generated without request"
         try:
diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py
index 11d289f..e36f919 100644
--- a/tests/regressiontests/views/views.py
+++ b/tests/regressiontests/views/views.py
@@ -6,6 +6,8 @@ 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.debug import sensitive
+from django.utils.log import getLogger
 
 from regressiontests.views import BrokenException, except_args
 
@@ -128,3 +130,22 @@ 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 non_sensitive_view(request):
+    try:
+        cooked_eggs = request.get('cooked_eggs')
+        raise Exception
+    except Exception:
+        logger = getLogger('django.request')
+        logger.error('Internal Server Error: %s' % request.path,
+            exc_info=sys.exc_info(),
+            extra={
+                'status_code': 500,
+                'request': request
+            }
+        )
+        return technical_500_response(request, *sys.exc_info())
+    
+@sensitive
+def sensitive_view(request):
+    return non_sensitive_view(request)
