From 863760c6f1a042984e2cd04e83ac537ca4d724a8 Mon Sep 17 00:00:00 2001 From: ghazi Date: Tue, 3 Jun 2025 06:56:56 +0100 Subject: [PATCH] default to a generic error message for unhandled exceptions to avoid unintentionally leaking sensitive data. fixes #99 --- docs/changelog.md | 25 +++++++++++++++++++++++++ docs/faq.md | 23 +++++++++++++++++++++++ drf_standardized_errors/handler.py | 5 ++++- tests/test_settings.py | 4 ++-- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 14fd5af..e5bfacc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -10,6 +10,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - add support for django 5.2 - add support for DRF 3.16 +### Changed (backward-incompatible) +- Unhandled exceptions now return a generic error message by default. This avoids unintentionally leaking +sensitive data included in the exception message. To revert to the old behavior or change the default error +message: + - create a custom exception handler class + ```python + from rest_framework.exceptions import APIException + from drf_standardized_errors.handler import ExceptionHandler + + class MyExceptionHandler(ExceptionHandler): + def convert_unhandled_exceptions(self, exc: Exception) -> APIException: + if not isinstance(exc, APIException): + # `return APIException(detail=str(exc))` restores the old behavior + return APIException(detail="New error message") + else: + return exc + ``` + - Then, update the settings to point to your exception handler class + ```python + DRF_STANDARDIZED_ERRORS = { + # ... + "EXCEPTION_HANDLER_CLASS": "path.to.MyExceptionHandler" + } + ``` + ## [0.14.1] - 2024-08-10 ### Added - declare support for django 5.1 diff --git a/docs/faq.md b/docs/faq.md index 1ee7f29..cf67cb4 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -43,3 +43,26 @@ You can check this [issue](https://github.com/ghazi-git/drf-standardized-errors/ for a possible solution. Still, `djangorestframework-camel-case` is built to work specifically with the default exception handler from DRF. It assumes that field names are the keys in the returned dict. So, that does not work well with this package. + + +## How can I change the default error message for unhandled exceptions + +You need to create a custom exception handler class +```python +from rest_framework.exceptions import APIException +from drf_standardized_errors.handler import ExceptionHandler + +class MyExceptionHandler(ExceptionHandler): + def convert_unhandled_exceptions(self, exc: Exception) -> APIException: + if not isinstance(exc, APIException): + return APIException(detail="New error message") + else: + return exc +``` +Then, update the settings to point to your exception handler class +```python +DRF_STANDARDIZED_ERRORS = { + # ... + "EXCEPTION_HANDLER_CLASS": "path.to.MyExceptionHandler" +} +``` diff --git a/drf_standardized_errors/handler.py b/drf_standardized_errors/handler.py index 695fb38..6e9d378 100644 --- a/drf_standardized_errors/handler.py +++ b/drf_standardized_errors/handler.py @@ -75,7 +75,10 @@ def convert_unhandled_exceptions(self, exc: Exception) -> exceptions.APIExceptio has a 500 status code. """ if not isinstance(exc, exceptions.APIException): - return exceptions.APIException(detail=str(exc)) + # return a generic error message to avoid potentially leaking sensitive + # data and match DRF/django behavior (same generic error message returned + # by django.views.defaults.server_error) + return exceptions.APIException(detail="Server Error (500)") else: return exc diff --git a/tests/test_settings.py b/tests/test_settings.py index f417e16..698bd61 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -41,7 +41,7 @@ def test_custom_exception_formatter_class(settings, api_client): assert response.status_code == 500 assert response.data["type"] == "server_error" assert response.data["code"] == "error" - assert response.data["message"] == "Internal server error." + assert response.data["message"] == "Server Error (500)" assert response.data["field_name"] is None @@ -82,7 +82,7 @@ def test_enable_in_debug_for_unhandled_exception_is_true( assert len(response.data["errors"]) == 1 error = response.data["errors"][0] assert error["code"] == "error" - assert error["detail"] == "Internal server error." + assert error["detail"] == "Server Error (500)" assert error["attr"] is None