Skip to content

Optional internal Python error masking with whitelist in GraphQL responses #1550

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions docs/error-masking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Custom GraphQL Error Masking
============================

This project includes a custom error formatting function for GraphQL
responses that masks sensitive error details from clients.

Purpose
-------

- Prevent exposing internal error details for security and user experience.
- Allow whitelisting of exception classes that should be exposed as-is.
- Return a generic error message for all other exceptions.

Configuration
-------------

You can control the behavior using the ``GRAPHENE_ERRORS`` setting in your
Django settings file under the ``GRAPHENE`` namespace:

.. code-block:: python

GRAPHENE = {
"GRAPHENE_ERRORS": {
"MASK_EXCEPTIONS": True, # Enable or disable masking
"ERROR_MESSAGE": "A custom error message.", # Defaults to "Something went wrong. Please try again later."
"WHITELISTED_EXCEPTIONS": [
"ValidationError", # Whitelist by class name
"django.core.exceptions.ValidationError", # Whitelist by full module path
"myapp.custom_exceptions.MyCustomException", # Custom exception whitelist by full path
],
}
}

Behavior
--------

- If ``MASK_EXCEPTIONS`` is False, all errors are returned fully formatted.
- If True, errors not in the whitelist will return only the generic message.
- Whitelisted exceptions are returned with full error details.

Usage
-----

The masking is automatically applied to the error responses of GraphQL
queries and mutations through a custom error formatter method.

You can modify or extend the whitelisted exceptions as needed to suit your
project's error handling policy.

1 change: 1 addition & 0 deletions graphene_django/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"ATOMIC_MUTATIONS": False,
"TESTING_ENDPOINT": "/graphql",
"MAX_VALIDATION_ERRORS": None,
"GRAPHENE_ERRORS": {},
}

if settings.DEBUG:
Expand Down
29 changes: 26 additions & 3 deletions graphene_django/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,32 @@ def get_response(self, request, data, show_graphiql=False):

if execution_result.errors:
set_rollback()
response["errors"] = [
self.format_error(e) for e in execution_result.errors
]

def safe_format(error):
config = getattr(graphene_settings, "GRAPHENE_ERRORS", {})
mask_exceptions = config.get("MASK_EXCEPTIONS", False)
error_message= config.get("ERROR_MESSAGE", "Something went wrong. Please try again later.")
whitelist = config.get("WHITELISTED_EXCEPTIONS", [])

if not mask_exceptions:
return self.format_error(error)

original_error = getattr(error, "original_error", None)
if not original_error:
return {"message": error_message}

error_class = type(original_error)
class_name = error_class.__name__
full_path = f"{error_class.__module__}.{class_name}"

if class_name in whitelist or full_path in whitelist:
return self.format_error(error)

formatted = self.format_error(error)
formatted["message"] = error_message
return formatted

response["errors"] = [safe_format(e) for e in execution_result.errors]

if execution_result.errors and any(
not getattr(e, "path", None) for e in execution_result.errors
Expand Down