Repository: pallets/werkzeug
Pull request #1592 (2019-06-25 21:00:44)

add BadRequestKeyError.show_exception
See pallets/flask#3249. The behavior introduced in 0.15.0 was to show the error unless `e.args` was empty. However, this makes it impossible for an error handler to know what the missing key was.

Instead, add a `show_exception` attribute to explicitly control the behavior. This defaults to false for a more secure default behavior. Now Flask can do:

```python
if app.debug:
    e.show_exception = True
```

While this is technically not backwards compatible, it adds information overall and sets a more secure default, and it was a fairly obscure and recent change to begin with, so I'm comfortable adding this to 0.15.5.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 96b06181..d29e0daa 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -9,6 +9,11 @@ Unreleased
     :issue:`1551`
 -   Fix a C assertion failure in debug builds of some Python 2.7
     releases. :issue:`1553`
+-   :class:`~exceptions.BadRequestKeyError` adds the ``KeyError``
+    message to the description if ``e.show_exception`` is set to
+    ``True``. This is a more secure default than the original 0.15.0
+    behavior and makes it easier to control without losing information.
+    (:pr:`1592`)
 
 
 Version 0.15.4
diff --git a/src/werkzeug/exceptions.py b/src/werkzeug/exceptions.py
index 9826e0f5..4b02bfa9 100644
--- a/src/werkzeug/exceptions.py
+++ b/src/werkzeug/exceptions.py
@@ -96,14 +96,21 @@ class HTTPException(Exception):
 
         The first argument to the class will be passed to the
         wrapped ``exception``, the rest to the HTTP exception. If
-        ``self.args`` is not empty, the wrapped exception message is
-        added to the HTTP exception description.
+        ``e.args`` is not empty and ``e.show_exception`` is ``True``,
+        the wrapped exception message is added to the HTTP error
+        description.
 
-        .. versionchanged:: 0.15
+        .. versionchanged:: 0.15.5
+            The ``show_exception`` attribute controls whether the
+            description includes the wrapped exception message.
+
+        .. versionchanged:: 0.15.0
             The description includes the wrapped exception message.
         """
 
         class newcls(cls, exception):
+            show_exception = False
+
             def __init__(self, arg=None, *args, **kwargs):
                 super(cls, self).__init__(*args, **kwargs)
 
@@ -115,7 +122,7 @@ class HTTPException(Exception):
             def get_description(self, environ=None):
                 out = super(cls, self).get_description(environ=environ)
 
-                if self.args:
+                if self.show_exception and self.args:
                     out += "<p><pre><code>{}: {}</code></pre></p>".format(
                         exception.__name__, escape(exception.__str__(self))
                     )
@@ -765,8 +772,8 @@ def abort(status, *args, **kwargs):
 _aborter = Aborter()
 
 
-#: an exception that is used internally to signal both a key error and a
-#: bad request.  Used by a lot of the datastructures.
+#: An exception that is used to signal both a :exc:`KeyError` and a
+#: :exc:`BadRequest`. Used by many of the datastructures.
 BadRequestKeyError = BadRequest.wrap(KeyError)
 
 # imported here because of circular dependencies of werkzeug.utils


Test patch:
diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py
index 20aabf62..97b4b205 100644
--- a/tests/test_datastructures.py
+++ b/tests/test_datastructures.py
@@ -583,11 +583,14 @@ class TestOrderedMultiDict(_MutableMultiDictTests):
         with pytest.raises(BadRequestKeyError) as exc_info:
             data["baz"]
 
+        assert "baz" not in exc_info.value.get_description()
+        exc_info.value.show_exception = True
         assert "baz" in exc_info.value.get_description()
 
         with pytest.raises(BadRequestKeyError) as exc_info:
             data.pop("baz")
 
+        exc_info.value.show_exception = True
         assert "baz" in exc_info.value.get_description()
         exc_info.value.args = ()
         assert "baz" not in exc_info.value.get_description()