Index: Werkzeug-0.14.1/docs/debug.rst
===================================================================
--- Werkzeug-0.14.1.orig/docs/debug.rst
+++ Werkzeug-0.14.1/docs/debug.rst
@@ -17,7 +17,8 @@ which makes it nearly impossible to use
 debugger allows the execution of arbitrary code which makes it a major
 security risk and **must never be used on production machines** because of
 that.  **We cannot stress this enough.  Do not enable this in
-production.**
+production.** Production means anything that is not development,
+and anything that is publicly accessible.
 
 Enabling the Debugger
 =====================
@@ -57,10 +58,9 @@ argument to get a detailed list of all t
 Debugger PIN
 ============
 
-Starting with Werkzeug 0.11 the debugger is additionally protected by a
-PIN.  This is a security helper to make it less likely for the debugger to
-be exploited in production as it has happened to people to keep the
-debugger active.  The PIN based authentication is enabled by default.
+The debug console is protected by a PIN. This is a security helper to make it
+less likely for the debugger to be exploited if you forget to disable it when
+deploying to production. The PIN based authentication is enabled by default.
 
 When the debugger comes up, on first usage it will prompt for a PIN that
 is printed to the command line.  The PIN is generated in a stable way that
@@ -77,6 +77,30 @@ restarted.
 intended to make it harder for an attacker to exploit the debugger.  Never
 enable the debugger in production.**
 
+Allowed Hosts
+-------------
+
+The debug console will only be served if the request comes from a trusted host.
+If a request comes from a browser page that is not served on a trusted URL, a
+400 error will be returned.
+
+By default, ``localhost``, any ``.localhost`` subdomain, and ``127.0.0.1`` are
+trusted. ``run_simple`` will trust its ``hostname`` argument as well. To change
+this further, use the debug middleware directly rather than through
+``use_debugger=True``.
+
+.. code-block:: python
+
+    if os.environ.get("USE_DEBUGGER") in {"1", "true"}:
+        app = DebuggedApplication(app, evalex=True)
+        app.trusted_hosts = [...]
+
+    run_simple("localhost", 8080, app)
+
+**This feature is not meant to entirely secure the debugger. It is
+intended to make it harder for an attacker to exploit the debugger.
+Never enable the debugger in production.**
+
 Pasting Errors
 ==============
 
Index: Werkzeug-0.14.1/werkzeug/debug/__init__.py
===================================================================
--- Werkzeug-0.14.1.orig/werkzeug/debug/__init__.py
+++ Werkzeug-0.14.1/werkzeug/debug/__init__.py
@@ -19,6 +19,8 @@ import hashlib
 import mimetypes
 from itertools import chain
 from os.path import join, dirname, basename, isfile
+from werkzeug.exceptions import SecurityError
+from werkzeug.wsgi import host_is_trusted
 from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
 from werkzeug.http import parse_cookie
 from werkzeug.debug.tbtools import get_current_traceback, render_console_html
@@ -276,6 +278,14 @@ class DebuggedApplication(object):
         else:
             self.pin = None
 
+        self.trusted_hosts = [".localhost", "127.0.0.1"]
+        """List of domains to allow requests to the debugger from. A leading dot
+        allows all subdomains. This only allows ``".localhost"`` domains by
+        default.
+
+        .. versionadded:: 3.0.3
+        """
+
     def _get_pin(self):
         if not hasattr(self, '_pin'):
             self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
@@ -331,19 +341,26 @@ class DebuggedApplication(object):
                     'sent.\n')
             else:
                 is_trusted = bool(self.check_pin_trust(environ))
-                yield traceback.render_full(evalex=self.evalex,
-                                            evalex_trusted=is_trusted,
-                                            secret=self.secret) \
+                yield traceback.render_full(
+                    evalex=self.evalex and self.check_host_trust(environ),
+                    evalex_trusted=is_trusted,
+                    secret=self.secret) \
                     .encode('utf-8', 'replace')
 
             traceback.log(environ['wsgi.errors'])
 
     def execute_command(self, request, command, frame):
         """Execute a command in a console."""
+
+        if not self.check_host_trust(request.environ):
+            return SecurityError()  # type: ignore[return-value]
         return Response(frame.console.eval(command), mimetype='text/html')
 
     def display_console(self, request):
         """Display a standalone shell."""
+
+        if not self.check_host_trust(request.environ):
+            return SecurityError()  # type: ignore[return-value]
         if 0 not in self.frames:
             if self.console_init_func is None:
                 ns = {}
@@ -392,12 +409,19 @@ class DebuggedApplication(object):
             return None
         return (time.time() - PIN_TIME) < int(ts)
 
+    def check_host_trust(self, environ):
+        return host_is_trusted(environ.get("HTTP_HOST"), self.trusted_hosts)
+
     def _fail_pin_auth(self):
         time.sleep(self._failed_pin_auth > 5 and 5.0 or 0.5)
         self._failed_pin_auth += 1
 
     def pin_auth(self, request):
         """Authenticates with the pin."""
+
+        if not self.check_host_trust(request.environ):
+            return SecurityError()  # type: ignore[return-value]
+
         exhausted = False
         auth = False
         trust = self.check_pin_trust(request.environ)
@@ -445,6 +469,9 @@ class DebuggedApplication(object):
 
     def log_pin_request(self):
         """Log the pin if needed."""
+        if not self.check_host_trust(request.environ):
+            return SecurityError()  # type: ignore[return-value]
+
         if self.pin_logging and self.pin is not None:
             _log('info', ' * To enable the debugger you need to '
                  'enter the security pin:')
@@ -472,7 +499,7 @@ class DebuggedApplication(object):
             elif cmd == 'pinauth' and secret == self.secret:
                 response = self.pin_auth(request)
             elif cmd == 'printpin' and secret == self.secret:
-                response = self.log_pin_request()
+                response = self.log_pin_request(request)  # type: ignore
             elif self.evalex and cmd is not None and frame is not None \
                     and self.secret == secret and \
                     self.check_pin_trust(environ):
Index: Werkzeug-0.14.1/werkzeug/debug/shared/debugger.js
===================================================================
--- Werkzeug-0.14.1.orig/werkzeug/debug/shared/debugger.js
+++ Werkzeug-0.14.1/werkzeug/debug/shared/debugger.js
@@ -69,7 +69,7 @@ $(function() {
       label.val('submitting...');
       $.ajax({
         dataType:     'json',
-        url:          document.location.pathname,
+        url:          document.location,
         data:         {__debugger__: 'yes', tb: TRACEBACK, cmd: 'paste',
                        s: SECRET},
         success:      function(data) {
@@ -100,7 +100,7 @@ function initPinBox() {
     btn.disabled = true;
     $.ajax({
       dataType: 'json',
-      url: document.location.pathname,
+      url: document.location,
       data: {__debugger__: 'yes', cmd: 'pinauth', pin: pin,
              s: SECRET},
       success: function(data) {
@@ -128,7 +128,7 @@ function initPinBox() {
 function promptForPin() {
   if (!EVALEX_TRUSTED) {
     $.ajax({
-      url: document.location.pathname,
+      url: document.location,
       data: {__debugger__: 'yes', cmd: 'printpin', s: SECRET}
     });
     $('.pin-prompt').fadeIn(function() {
Index: Werkzeug-0.14.1/werkzeug/serving.py
===================================================================
--- Werkzeug-0.14.1.orig/werkzeug/serving.py
+++ Werkzeug-0.14.1/werkzeug/serving.py
@@ -768,6 +768,9 @@ def run_simple(hostname, port, applicati
     if use_debugger:
         from werkzeug.debug import DebuggedApplication
         application = DebuggedApplication(application, use_evalex)
+        # Allow the specified hostname to use the debugger, in addition to
+        # localhost domains.
+        application.trusted_hosts.append(hostname)
     if static_files:
         from werkzeug.wsgi import SharedDataMiddleware
         application = SharedDataMiddleware(application, static_files)
