From 4d595e4a128ccee779801b1bd21baea79b127297 Mon Sep 17 00:00:00 2001 From: Lucas Cabello Date: Wed, 27 May 2026 14:57:45 -0400 Subject: [PATCH] Strip version info from status endpoint for unauthenticated requests The /pulp/api/v3/status/ endpoint disclosed exact versions of every installed Pulp plugin to unauthenticated users. Attackers can use exact version numbers for targeted CVE lookups. Remove authentication_classes override so DRF runs its default authenticators, and strip the versions field from the response when the request is unauthenticated. The endpoint remains accessible to everyone (permission_classes stays empty) so healthcheck probes continue to work. AI-assisted: Generated with Claude Code (claude.ai/code) Co-Authored-By: Claude Opus 4.6 (1M context) --- pulpcore/app/serializers/status.py | 4 +++- pulpcore/app/views/status.py | 5 +++-- pulpcore/tests/functional/api/test_status.py | 11 +++++++---- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pulpcore/app/serializers/status.py b/pulpcore/app/serializers/status.py index 8adb8458973..7649a6955ea 100644 --- a/pulpcore/app/serializers/status.py +++ b/pulpcore/app/serializers/status.py @@ -96,7 +96,9 @@ class StatusSerializer(serializers.Serializer): Serializer for the status information of the app """ - versions = VersionSerializer(help_text=_("Version information of Pulp components"), many=True) + versions = VersionSerializer( + help_text=_("Version information of Pulp components"), many=True, required=False + ) online_workers = AppStatusSerializer( help_text=_( diff --git a/pulpcore/app/views/status.py b/pulpcore/app/views/status.py index 7d494e272f2..22b496c8fd8 100644 --- a/pulpcore/app/views/status.py +++ b/pulpcore/app/views/status.py @@ -38,8 +38,6 @@ class StatusView(APIView): Returns status information about the application """ - # allow anyone to access the status api - authentication_classes = [] permission_classes = [] @extend_schema( @@ -99,6 +97,9 @@ def get(self, request): "domain_enabled": settings.DOMAIN_ENABLED, } + if not request.user or not request.user.is_authenticated: + data.pop("versions") + context = {"request": request} serializer = StatusSerializer(data, context=context) return Response(serializer.data) diff --git a/pulpcore/tests/functional/api/test_status.py b/pulpcore/tests/functional/api/test_status.py index ae404a5a122..908a6fbd254 100644 --- a/pulpcore/tests/functional/api/test_status.py +++ b/pulpcore/tests/functional/api/test_status.py @@ -49,7 +49,6 @@ "database_connection", "online_workers", "storage", - "versions", ], } @@ -65,14 +64,18 @@ def test_get_authenticated(pulpcore_bindings, pulp_settings): @pytest.mark.parallel -def test_get_unauthenticated(pulpcore_bindings, anonymous_user, pulp_settings): +def test_get_unauthenticated(pulpcore_bindings, anonymous_user): """GET the status path with no credentials. - Verify the response with :meth:`verify_get_response`. + Verify that version information is not disclosed to unauthenticated users. """ with anonymous_user: response = pulpcore_bindings.StatusApi.status_read() - verify_get_response(response.to_dict(), STATUS, pulp_settings) + status = response.to_dict() + validate(status, STATUS) + assert "versions" not in status or status["versions"] is None + assert status["database_connection"]["connected"] + assert status["online_workers"] != [] @pytest.mark.parallel