From d026d22a221a654eeba83706171ff5db0fd49df3 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 4 Mar 2026 13:03:33 -0500 Subject: [PATCH] Closes #21468: copy_safe_request() should retain non-sensitive HTTP request headers --- netbox/utilities/constants.py | 8 ++++++ netbox/utilities/request.py | 15 ++++++----- netbox/utilities/tests/test_request.py | 37 +++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/netbox/utilities/constants.py b/netbox/utilities/constants.py index c2ffa37a8..21e3810d6 100644 --- a/netbox/utilities/constants.py +++ b/netbox/utilities/constants.py @@ -38,6 +38,7 @@ FILTER_TREENODE_NEGATION_LOOKUP_MAP = dict( # HTTP Request META safe copy # +# Non-HTTP_ META keys to include when copying a request (whitelist) HTTP_REQUEST_META_SAFE_COPY = [ 'CONTENT_LENGTH', 'CONTENT_TYPE', @@ -61,6 +62,13 @@ HTTP_REQUEST_META_SAFE_COPY = [ 'SERVER_PORT', ] +# HTTP_ META keys known to carry sensitive data; excluded when copying a request (denylist) +HTTP_REQUEST_META_SENSITIVE = { + 'HTTP_AUTHORIZATION', + 'HTTP_COOKIE', + 'HTTP_PROXY_AUTHORIZATION', +} + # # CSV-style format delimiters diff --git a/netbox/utilities/request.py b/netbox/utilities/request.py index da642ba24..54a7f9ff6 100644 --- a/netbox/utilities/request.py +++ b/netbox/utilities/request.py @@ -8,7 +8,7 @@ from netaddr import AddrFormatError, IPAddress from netbox.registry import registry -from .constants import HTTP_REQUEST_META_SAFE_COPY +from .constants import HTTP_REQUEST_META_SAFE_COPY, HTTP_REQUEST_META_SENSITIVE __all__ = ( 'NetBoxFakeRequest', @@ -45,11 +45,14 @@ def copy_safe_request(request, include_files=True): request: The original request object include_files: Whether to include request.FILES. """ - meta = { - k: request.META[k] - for k in HTTP_REQUEST_META_SAFE_COPY - if k in request.META and isinstance(request.META[k], str) - } + meta = {} + for k, v in request.META.items(): + if not isinstance(v, str): + continue + if k in HTTP_REQUEST_META_SAFE_COPY: + meta[k] = v + elif k.startswith('HTTP_') and k not in HTTP_REQUEST_META_SENSITIVE: + meta[k] = v data = { 'META': meta, 'COOKIES': request.COOKIES, diff --git a/netbox/utilities/tests/test_request.py b/netbox/utilities/tests/test_request.py index 46580cc38..3ab8d6e1c 100644 --- a/netbox/utilities/tests/test_request.py +++ b/netbox/utilities/tests/test_request.py @@ -1,7 +1,42 @@ +from django.contrib.auth.models import AnonymousUser from django.test import RequestFactory, TestCase from netaddr import IPAddress -from utilities.request import get_client_ip +from utilities.request import copy_safe_request, get_client_ip + + +class CopySafeRequestTests(TestCase): + def setUp(self): + self.factory = RequestFactory() + + def _make_request(self, **kwargs): + request = self.factory.get('/', **kwargs) + request.user = AnonymousUser() + return request + + def test_standard_meta_keys_copied(self): + request = self._make_request(HTTP_USER_AGENT='TestAgent/1.0') + fake = copy_safe_request(request) + self.assertEqual(fake.META.get('HTTP_USER_AGENT'), 'TestAgent/1.0') + + def test_arbitrary_http_headers_copied(self): + """Arbitrary HTTP_ headers (e.g. X-NetBox-*) should be included.""" + request = self._make_request(HTTP_X_NETBOX_BRANCH='my-branch') + fake = copy_safe_request(request) + self.assertEqual(fake.META.get('HTTP_X_NETBOX_BRANCH'), 'my-branch') + + def test_sensitive_headers_excluded(self): + """Authorization and Cookie headers must not be copied.""" + request = self._make_request(HTTP_AUTHORIZATION='Bearer secret') + fake = copy_safe_request(request) + self.assertNotIn('HTTP_AUTHORIZATION', fake.META) + + def test_non_string_meta_values_excluded(self): + """Non-string META values must not be copied.""" + request = self._make_request() + request.META['HTTP_X_CUSTOM_INT'] = 42 + fake = copy_safe_request(request) + self.assertNotIn('HTTP_X_CUSTOM_INT', fake.META) class GetClientIPTests(TestCase):