Repository: pypi/warehouse

Issue:
Protect against X-Vhm-Root headers
Protect against X-Vhm-Root headers (See https://github.com/Pylons/pyramid/issues/1591).



Discussion:

Resolving patch:
diff --git a/warehouse/config.py b/warehouse/config.py
--- a/warehouse/config.py
+++ b/warehouse/config.py
@@ -23,8 +23,8 @@
 from pyramid.response import Response
 
 from warehouse import __commit__
-from warehouse.utils.proxy import ProxyFixer
 from warehouse.utils.static import WarehouseCacheBuster
+from warehouse.utils.wsgi import ProxyFixer, VhmRootRemover
 
 
 class Environment(enum.Enum):
@@ -314,6 +314,9 @@ def configure(settings=None):
         token=config.registry.settings["warehouse.token"],
     )
 
+    # Protect against cache poisoning via the X-Vhm-Root headers.
+    config.add_wsgi_middleware(VhmRootRemover)
+
     # Scan everything for configuration
     config.scan(ignore=["warehouse.migrations.env"])
 
diff --git a/warehouse/utils/proxy.py b/warehouse/utils/wsgi.py
similarity index 86%
rename from warehouse/utils/proxy.py
rename to warehouse/utils/wsgi.py
--- a/warehouse/utils/proxy.py
+++ b/warehouse/utils/wsgi.py
@@ -49,3 +49,16 @@ def __call__(self, environ, start_response):
 
         # Dispatch to the real underlying application.
         return self.app(environ, start_response)
+
+
+class VhmRootRemover:
+
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        # Delete the X-Vhm-Root header if it exists.
+        if "HTTP_X_VHM_ROOT" in environ:
+            del environ["HTTP_X_VHM_ROOT"]
+
+        return self.app(environ, start_response)


Test patch:
diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py
--- a/tests/unit/test_config.py
+++ b/tests/unit/test_config.py
@@ -20,7 +20,7 @@
 from pyramid.httpexceptions import HTTPMovedPermanently
 
 from warehouse import config
-from warehouse.utils.proxy import ProxyFixer
+from warehouse.utils.wsgi import ProxyFixer, VhmRootRemover
 
 
 class TestCSPTween:
@@ -308,6 +308,7 @@ def __init__(self):
     assert result is configurator_obj
     assert configurator_obj.add_wsgi_middleware.calls == [
         pretend.call(ProxyFixer, token="insecure token"),
+        pretend.call(VhmRootRemover),
     ]
     assert configurator_obj.include.calls == (
         [
diff --git a/tests/unit/utils/test_proxy.py b/tests/unit/utils/test_wsgi.py
similarity index 69%
rename from tests/unit/utils/test_proxy.py
rename to tests/unit/utils/test_wsgi.py
--- a/tests/unit/utils/test_proxy.py
+++ b/tests/unit/utils/test_wsgi.py
@@ -12,7 +12,7 @@
 
 import pretend
 
-from warehouse.utils import proxy
+from warehouse.utils import wsgi
 
 
 class TestProxyFixer:
@@ -29,7 +29,7 @@ def test_cleans_environ_forwarded(self):
         }
         start_response = pretend.stub()
 
-        resp = proxy.ProxyFixer(app, token=None)(environ, start_response)
+        resp = wsgi.ProxyFixer(app, token=None)(environ, start_response)
 
         assert resp is response
         assert app.calls == [
@@ -48,7 +48,7 @@ def test_skips_headers(self):
         }
         start_response = pretend.stub()
 
-        resp = proxy.ProxyFixer(app, token="1234")(environ, start_response)
+        resp = wsgi.ProxyFixer(app, token="1234")(environ, start_response)
 
         assert resp is response
         assert app.calls == [pretend.call({}, start_response)]
@@ -65,7 +65,7 @@ def test_accepts_headers(self):
         }
         start_response = pretend.stub()
 
-        resp = proxy.ProxyFixer(app, token="1234")(environ, start_response)
+        resp = wsgi.ProxyFixer(app, token="1234")(environ, start_response)
 
         assert resp is response
         assert app.calls == [
@@ -86,7 +86,34 @@ def test_missing_headers(self):
         environ = {"HTTP_WAREHOUSE_TOKEN": "1234"}
         start_response = pretend.stub()
 
-        resp = proxy.ProxyFixer(app, token="1234")(environ, start_response)
+        resp = wsgi.ProxyFixer(app, token="1234")(environ, start_response)
 
         assert resp is response
         assert app.calls == [pretend.call({}, start_response)]
+
+
+class TestVhmRootRemover:
+
+    def test_removes_header(self):
+        response = pretend.stub()
+        app = pretend.call_recorder(lambda e, s: response)
+        environ = {"HTTP_X_VHM_ROOT": "/foo/bar"}
+        start_response = pretend.stub()
+
+        resp = wsgi.VhmRootRemover(app)(environ, start_response)
+
+        assert resp is response
+        assert app.calls == [pretend.call({}, start_response)]
+
+    def test_passes_through_headers(self):
+        response = pretend.stub()
+        app = pretend.call_recorder(lambda e, s: response)
+        environ = {"HTTP_X_FOOBAR": "wat"}
+        start_response = pretend.stub()
+
+        resp = wsgi.VhmRootRemover(app)(environ, start_response)
+
+        assert resp is response
+        assert app.calls == [
+            pretend.call({"HTTP_X_FOOBAR": "wat"}, start_response),
+        ]