skip to content

How do you keep django-debug-toolbar and django-silk out of a Django production deployment, and what leaks if one slips through?

level: seniorimportance: should knowfreq 40%

answer

  1. development settings module
  2. toolbar guards: DEBUG and IPs
  3. silk has no DEBUG check
  4. open /silk/ by default
  5. proxy address in INTERNAL_IPS

basics

~20 s

Install both only in a development settings module and dependency group. The toolbar's default guard needs DEBUG and INTERNAL_IPS, but silk has none: it records bodies and SQL and serves /silk/ to anyone unless its authentication settings are on.

solid answer

~40 s

The reliable approach is structural: both packages live in a development dependency group, and a `settings/dev.py` adds `debug_toolbar` and `silk` to `INSTALLED_APPS` and `MIDDLEWARE`, with the URLs included only when the app is installed. The toolbar has guards of its own: the default `SHOW_TOOLBAR_CALLBACK` needs `DEBUG = True` and `REMOTE_ADDR` in `INTERNAL_IPS`, and `debug_toolbar_urls()` returns nothing without `DEBUG`. They fail if `DEBUG` is on in production, if a custom callback returns `True`, or if a proxy's address sits in `INTERNAL_IPS`, since every visitor then arrives from it. Silk has **no** `DEBUG` check: it keeps recording, stores bodies, headers and SQL, and serves `/silk/` to anyone unless `SILKY_AUTHENTICATION` and `SILKY_AUTHORISATION` are `True`. Its masking only covers keys matching `SILKY_SENSITIVE_KEYS`, so a `card_number` field is stored in clear.

code

python · 25 lines
python
# settings/dev.py
from .base import *  # noqa: F403

DEBUG = True
INTERNAL_IPS = ["127.0.0.1"]
INSTALLED_APPS = [*INSTALLED_APPS, "debug_toolbar", "silk"]  # noqa: F405
MIDDLEWARE = [
    "debug_toolbar.middleware.DebugToolbarMiddleware",
    "silk.middleware.SilkyMiddleware",
    *MIDDLEWARE,  # noqa: F405  (no GZipMiddleware in this project)
]

# urls.py
from django.apps import apps
from django.urls import include, path

urlpatterns = [
    # ... the project's own routes ...
]
if apps.is_installed("debug_toolbar"):
    from debug_toolbar.toolbar import debug_toolbar_urls

    urlpatterns += debug_toolbar_urls()
if apps.is_installed("silk"):
    urlpatterns += [path("silk/", include("silk.urls", namespace="silk"))]

go deeper

for a junior

Remember that both tools are for development only and belong in development settings, never in production.

for a middle

Explain the toolbar's DEBUG and INTERNAL_IPS guard and why silk, lacking any such guard, is the more dangerous one to leave installed.

for a senior

Spot the ways guards get defeated, such as proxy addresses, permissive callbacks and copied settings, and lock silk down when a shared environment needs it.

for a principal

Set a policy that development tooling never ships in the production artefact and that changes to its gating settings get security review.

## What each tool exposes Both tools exist to show internals, which is exactly what must not reach the public. | Tool | What an outsider could see or do | |---|---| | django-debug-toolbar | every SQL statement with its parameters, template context, request data and session, headers, cache keys, signal receivers, settings (filtered through Django's `SafeExceptionReporterFilter`), stack traces with file paths; and the SQL panel's endpoints re-run `SELECT` statements and `EXPLAIN`, which on PostgreSQL is `EXPLAIN ANALYZE` and executes the query | | django-silk | stored request and response bodies and headers for thousands of past requests, every SQL query with parameters, profiles; a UI at `/silk/` to browse them | ## The toolbar's own guards django-debug-toolbar is built to stay dark outside development: - the default `SHOW_TOOLBAR_CALLBACK`, `debug_toolbar.middleware.show_toolbar`, returns `False` unless `settings.DEBUG` is `True`, and then requires `REMOTE_ADDR` to be in `INTERNAL_IPS`; - `debug_toolbar_urls()` returns an empty list when `DEBUG` is `False`. How teams defeat those guards anyway: 1. **`DEBUG = True` in production.** That alone is a serious leak through Django's own error pages, and it re-arms the toolbar. 2. **A permissive callback.** `"SHOW_TOOLBAR_CALLBACK": lambda request: True` to see the toolbar on staging stays when the settings are copied. 3. **The proxy's address in `INTERNAL_IPS`.** Behind a reverse proxy or load balancer, `REMOTE_ADDR` is the proxy for every visitor, so listing it makes every visitor internal. 4. **`show_toolbar_with_docker` outside a container.** Its docs warn that its gateway guess can then match another machine on the network. ## django-silk has no such guard Silk never looks at `DEBUG`. If `silk` is in `INSTALLED_APPS` and `SilkyMiddleware` in `MIDDLEWARE`, it records. Its defaults are development defaults: - `SILKY_AUTHENTICATION` and `SILKY_AUTHORISATION` are `False`, so **anybody** can open `/silk/`; with both `True`, the default `SILKY_PERMISSIONS` admits staff users only; - `SILKY_INTERCEPT_PERCENT` is `100`, so every request is written to the database; - `SILKY_MAX_RECORDED_REQUESTS` is `10**4`, trimmed on a share of requests, so the tables hold a large history; - masking is by key name: `SILKY_SENSITIVE_KEYS` defaults to `username`, `api`, `token`, `key`, `secret`, `password` and `signature`, matched against body keys and header names, with cookies hidden by `SILKY_HIDE_COOKIES`. A body field called `card_number` or `iban` is stored as is, and SQL parameters are stored unmasked; - `SILKY_ANALYZE_QUERIES` is `False`, and should stay so: silk's docs warn that turning it on can execute queries a second time. ## Keeping them out, structurally - **Dependencies**: install both only in a development dependency group, so the production image cannot import them. - **Settings**: a `settings/dev.py` that imports the base settings and appends the apps and middleware; production settings never mention them. - **URLs**: include the toolbar's and silk's URLs only when the app is installed, using `django.apps.apps.is_installed()`. - **Tests**: skip the toolbar during tests; its `debug_toolbar.E001` check flags setups likely to break there. - **Review**: treat any change to `INTERNAL_IPS`, `SHOW_TOOLBAR_CALLBACK` or silk's settings in shared settings files as a security-relevant change. ## If silk must run on a shared environment Sometimes a team wants recordings from staging. Then turn on `SILKY_AUTHENTICATION` and `SILKY_AUTHORISATION`, sample with `SILKY_INTERCEPT_PERCENT` or `SILKY_INTERCEPT_FUNC`, cap bodies with `SILKY_MAX_REQUEST_BODY_SIZE` and `SILKY_MAX_RESPONSE_BODY_SIZE`, extend `SILKY_SENSITIVE_KEYS` with your own field names, and clear old data with the `silk_clear_request_log` management command. Staging with real customer data is still production data. ## A quick pre-release check 1. Build the production image and list its installed packages: neither tool should be there. 2. Start it with production settings and request `/__debug__/` and `/silk/`: both should return 404. 3. Grep shared settings for `INTERNAL_IPS`, `SHOW_TOOLBAR_CALLBACK` and `SILKY_`. Three minutes of checking closes a leak that would otherwise hand an attacker the database's queries and every recorded request body.

  • Behind a reverse proxy, how would you let only developers see django-debug-toolbar on a staging Django site?
    Do not add the proxy's address to `INTERNAL_IPS`, because every request arrives from it. Write a `SHOW_TOOLBAR_CALLBACK` that requires `DEBUG` and an authenticated staff user, or reach staging over a private network where `REMOTE_ADDR` is the developer's own address. Keep that callback in the staging settings only.
  • Why is django-silk riskier than the toolbar if it is accidentally left in a production Django deployment?
    The toolbar's default callback still requires `DEBUG = True` and an internal address, so it usually stays dark. Silk checks neither: it records every request's bodies, headers and SQL into the database and, with its authentication settings off by default, serves them to anyone who opens `/silk/`.

saying these in an interview costs you the question

  • django-silk stops recording automatically when DEBUG is False
  • Adding the load balancer's IP to INTERNAL_IPS is a safe staging trick
  • The /silk/ UI requires a staff login by default
  • SILKY_SENSITIVE_KEYS masks every personal field in stored bodies
  • debug_toolbar_urls() keeps mounting the toolbar views when DEBUG is off