What does Django's SafeExceptionReporterFilter hide by default, and when would you replace it through DEFAULT_EXCEPTION_REPORTER_FILTER?
answer
- a regex over names
- settings, META, cookies always
- locals and POST only when active
- subclass, then point the setting
basics
~20 sSafeExceptionReporterFilter always stars settings, META entries and cookies whose names match API, AUTH, TOKEN, KEY, SECRET, PASS, SIGNATURE or HTTP_COOKIE, and, when DEBUG is False, the decorator-marked POST fields and locals. Subclass it and set DEFAULT_EXCEPTION_REPORTER_FILTER to widen the rules.
solid answer
~40 s`SafeExceptionReporterFilter` in `django.views.debug` is the default of `DEFAULT_EXCEPTION_REPORTER_FILTER`. On every report it passes settings, `request.META` and cookies through `cleanse_setting`: any name matching the `hidden_settings` regex (`API|AUTH|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE`, case-insensitive, partial match), plus the session cookie, becomes `cleansed_substitute`, and nested dicts such as `DATABASES` are cleaned key by key. POST fields and frame locals are only cleaned when `is_active()` is true, which by default means `DEBUG` is `False`, and only for names marked by the decorators. Replace it when your names do not match, such as a `DATABASE_URL` with a password in it, or when you want filtering even in debug mode: subclass, override `hidden_settings` or `is_active()`, and point the setting at the class. One view can swap the filter through `request.exception_reporter_filter`.
code
python · 19 lines# config/errors.py
import re
from django.views.debug import SafeExceptionReporterFilter
class StrictReporterFilter(SafeExceptionReporterFilter):
hidden_settings = re.compile(
r"API|AUTH|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE|DATABASE_URL|DSN|CREDENTIALS",
flags=re.IGNORECASE,
)
def is_active(self, request):
# Also scrub marked POST fields and locals on staging, which runs with DEBUG on.
return True
# settings.py
DEFAULT_EXCEPTION_REPORTER_FILTER = "config.errors.StrictReporterFilter"go deeper
Know that Django stars settings with names like SECRET_KEY or PASSWORD on error reports, and that other names are shown.
Explain the hidden_settings pattern, that settings, META and cookies are always cleaned while POST and locals depend on is_active(), and the setting that swaps the class.
Spot name-pattern gaps such as connection strings and custom headers, and write a subclass with a wider pattern or an is_active() for debug-mode staging.
Decide where redaction lives: a shared filter class owned centrally, a naming convention for secret settings, and review of new settings against the pattern.
## Two settings, two classes Django builds every error report, the technical 500 page and the ADMINS email alike, from two pluggable pieces: | Setting | Default | Job | |---|---|---| | `DEFAULT_EXCEPTION_REPORTER` | `django.views.debug.ExceptionReporter` | collects the traceback, request and settings, and renders the HTML and text reports | | `DEFAULT_EXCEPTION_REPORTER_FILTER` | `django.views.debug.SafeExceptionReporterFilter` | decides which values in that report are replaced by stars | Both can also be overridden for a single request by setting `request.exception_reporter_class` or `request.exception_reporter_filter` inside a view. ## What the default filter always hides The filter's `cleanse_setting()` decides by **name**, not by value. A name is sensitive when: - it matches the class attribute **`hidden_settings`**, a case-insensitive regex equivalent to `API|AUTH|TOKEN|KEY|SECRET|PASS|SIGNATURE|HTTP_COOKIE`, as a **partial** match (so `PASS` catches `EMAIL_HOST_PASSWORD` and `TOKEN` catches `TOKENIZED`); - or it equals `SESSION_COOKIE_NAME`. Sensitive values become `cleansed_substitute`, twenty asterisks by default. The cleaning recurses into dictionaries, so `DATABASES['default']['PASSWORD']` is starred while the host and name stay readable. It is applied to three sources, **regardless of `DEBUG`**: 1. every upper-case setting (`get_safe_settings()`); 2. `request.META` (`get_safe_request_meta()`), so `HTTP_AUTHORIZATION`, `HTTP_COOKIE` and `HTTP_X_API_KEY` are hidden; 3. `request.COOKIES` (`get_safe_cookies()`). ## What it hides only when active `is_active(request)` returns `settings.DEBUG is False`. When it is true, the filter also: - stars the POST fields listed by `sensitive_post_parameters` (`get_post_parameters()`), including inside any `QueryDict` found among locals; - stars the locals listed by `sensitive_variables` (`get_traceback_frame_variables()`). In debug mode these two stay in clear by design; the method's own docstring says a site with `DEBUG` on is not safe anyway. ## Where the default is not enough Because the rule is a name pattern, it misses values whose names look innocent: - a connection-string setting such as `DATABASE_URL = "postgres://app:s3cret@db/shop"`, since `DATABASE_URL` contains none of the words; - a setting called `PAYMENT_CREDENTIALS` or `SMTP_LOGIN`; - a custom request header whose name matches none of the words, such as `X-Signed-Session`. It also cannot help with anything that is not a setting, header, cookie, POST field or marked local: exception messages, a model instance's `repr()` in a frame, or your own log lines. ## Replacing it Subclass `SafeExceptionReporterFilter` and override what you need: - `hidden_settings` to widen the name pattern (for example add `DATABASE_URL|DSN|CREDENTIALS`); - `cleansed_substitute` to change the replacement text; - `is_active()` to filter on a staging site that runs with `DEBUG = True`, or to decide per request; - `get_post_parameters()` or `get_traceback_frame_variables()` for custom rules. Then set `DEFAULT_EXCEPTION_REPORTER_FILTER` to the class's dotted path. Django imports the class and caches a single instance, so keep the filter stateless. For one sensitive view, assign `request.exception_reporter_filter = StrictReporterFilter()` at the top of the view instead. ## A related hook: the reporter If you need to change what a report contains rather than what it hides, for example to add the tenant ID or to drop the settings section, subclass `ExceptionReporter`, override `get_traceback_data()`, and set `DEFAULT_EXCEPTION_REPORTER`. The filter and the reporter are separate on purpose: one decides visibility, the other layout and content. ## A quick audit before launch 1. Trigger a deliberate error on a staging copy with `DEBUG = False` and read the report that arrives, section by section. 2. List every setting read from the environment and check its **name** against the pattern; rename (`DATABASE_PASSWORD` instead of a URL) or widen `hidden_settings`. 3. Check custom request headers that carry credentials, since they appear in `META` as `HTTP_...` names. 4. Grep views that handle personal or payment data for `sensitive_post_parameters` and `sensitive_variables`, and for exception messages that format those values. 5. Record the decision: which filter class is active, who owns its pattern, and which environments run with `DEBUG` on. The filter is small and predictable, which is its strength: once you know it only reads names, its gaps are easy to list and close.
- In Django, how does one view use a stricter error-report filter than the rest of the site?Assign an instance to the request inside the view: `request.exception_reporter_filter = StrictReporterFilter()`. The reporter reads that attribute before falling back to the class named in `DEFAULT_EXCEPTION_REPORTER_FILTER`. The same pattern works for `request.exception_reporter_class` when a view needs a different report layout.
saying these in an interview costs you the question
- The filter inspects values and hides anything that looks like a password
- Settings are only filtered when DEBUG is False
- DATABASE_URL is hidden because it contains credentials
- A LOGGING filter can redact fields inside the error report
- The default filter removes sensitive settings from the report entirely