Skip to content

Remove duplicate CommonMiddleware from MIDDLEWARE#2436

Open
pyxelr wants to merge 1 commit into
codalab:developfrom
pyxelr:fix/duplicate-common-middleware
Open

Remove duplicate CommonMiddleware from MIDDLEWARE#2436
pyxelr wants to merge 1 commit into
codalab:developfrom
pyxelr:fix/duplicate-common-middleware

Conversation

@pyxelr

@pyxelr pyxelr commented Jun 23, 2026

Copy link
Copy Markdown

Summary

django.middleware.common.CommonMiddleware is listed twice in the MIDDLEWARE tuple in src/settings/base.py. This PR removes the second occurrence.

Why

Per Django's middleware onion model, each entry in MIDDLEWARE is an independent layer whose process_response runs on every request. Duplicating CommonMiddleware therefore runs its process_request once but its process_response twice per request, which has two effects:

  1. Content-Length is computed twice on every response (harmless but wasteful).

  2. Real symptom that brought this to our attention — for users whose django_session.session_data row cannot be verified by signing.loads() (e.g. after a SECRET_KEY rotation), the second process_response pass raises an exception that Django converts to a generic 500 page. No traceback is preserved in logs (Django wraps the exception before logging), only the one-line django.utils.log:log_response - Internal Server Error: <path> is emitted — logged twice, which is itself a side effect of the duplicate. We did not isolate the exact line that raises; likely candidates are the second-pass should_redirect_with_slash check or downstream middleware reading from the half-initialised session (e.g. MessageMiddleware's FallbackStorage), but this is inferred from behaviour, not from a captured stack trace.

    The 500 is observed on every non-200 path for affected users:

    • any APPEND_SLASH redirect (e.g. /competitions/8/competitions/8/)
    • any 404 path (/competitions/<missing_id>/, /forums/null/, /favicon.ico, …)
    • URLs that match a real view return 200 correctly, because the session-decode failure is silently swallowed (decode returns {} and the user is treated as anonymous).

Removing the duplicate restores the standard Django middleware behaviour. A corrupted session decodes to {} exactly once via SessionMiddleware, the user falls back to AnonymousUser, and 404/redirect responses are returned normally.

Reproduction (Django 5.2, SESSION_ENGINE='django.contrib.sessions.backends.db')

Path Anonymous Authenticated (valid session) Authenticated (un-verifiable session row)
/competitions/8/ (exists) 200 200 200
/competitions/8 (no slash) 301 301 500
/competitions/<missing>/ 404 404 500
/favicon.ico 404 404 500

Steps to reproduce:

  1. Insert a row in django_session whose session_data was signed with a different SECRET_KEY (or simply replace any existing row's session_data with arbitrary signing.dumps(...) output produced under a different key).
  2. Send the corresponding sessionid cookie to any non-200 path on the running site.
  3. Get a 500 Server Error.

After removing the duplicate, the same request returns the expected 301 / 404.

Diff

@@ MIDDLEWARE = (
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
     # 'corsheaders.middleware.CorsMiddleware', # BB
-    'django.middleware.common.CommonMiddleware',
     'middleware.BlockBannedUsersMiddleware'
 )

Risk

Minimal — restores the documented Django middleware ordering pattern (a single CommonMiddleware between SessionMiddleware and CsrfViewMiddleware). No other settings file overrides this tuple in a way that depends on the duplicate. Patch has been running on a live deployment for ~24h with no regressions observed.

django.middleware.common.CommonMiddleware was listed twice in the MIDDLEWARE tuple.

Symptoms when the chain runs twice:

* For users whose django_session.session_data row cannot be verified by
  signing.loads (e.g. after a SECRET_KEY rotation), the second pass of
  CommonMiddleware.process_response runs against responses for which the
  session/messages state ends up half-initialised, surfacing as a generic
  500 page with no traceback on every 404 path, every APPEND_SLASH 301,
  and assets like /favicon.ico. With-slash URLs that match a real view
  return 200 because the corruption is silently swallowed (decode -> {}).
* Content-Length is computed twice on every response.

Removing the duplicate restores the standard middleware behaviour: a
corrupted session decodes to {} once, the user is treated as anonymous,
and 404/redirect responses are returned normally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant