Conversation
…paces ApisixUserMiddleware trusts the X-Userinfo header (base64-encoded JSON identity claims) unconditionally, with no signature or secret tying it to a real Keycloak login through APISIX -- it just assumes the header could only be there because APISIX put it there. The default local dev profile (COMPOSE_PROFILES=backend,frontend) never runs APISIX/Keycloak, and Codespaces doesn't either while also exposing forwardable, network-reachable ports -- so in both cases nothing verifies that header before Django trusts it. A request straight to Django with a forged X-Userinfo header logs in (and creates, if the identity doesn't exist) as whoever the header claims. Verified live, end to end: reproduced the exact repro from the linked issue against a clean checkout (forged header -> is_authenticated: true, brand-new account) with docker compose up (no APISIX/Keycloak profile), then confirmed the same request is fully rejected (is_authenticated: false, no account created) once DISABLE_APISIX_USER_MIDDLEWARE=True is set. DISABLE_APISIX_USER_MIDDLEWARE was already the purpose-built lever for this -- traced through Django's RemoteUserMiddleware base class and confirmed AUTHENTICATION_BACKENDS has no remote_user-capable backend, so this is a safe no-op for real session-based Django logins. QA/ Production are unaffected (they don't set this anywhere; APISIX is the only path in there, confirmed separately in the issue). Existing middleware/admin tests set this explicitly per-test rather than relying on the env default, and aren't affected either (42 passed). Set the default in both env/backend.env and env/codespaces.env, since codespaces.env doesn't inherit from backend.env (extends replaces its env_file list rather than merging). Updated README-keycloak.md's opt-in-to-Keycloak instructions to add the now- necessary DISABLE_APISIX_USER_MIDDLEWARE=False override, and corrected its now-inaccurate "Keycloak is enabled by default" claim. Fixes mitodl/hq#13471 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
OpenAPI ChangesNo changes detected Unexpected changes? Ensure your branch is up-to-date with |
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The new backend default causes existing middleware tests to run with APISIX handling disabled and fail in a fresh Docker Compose environment.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
This PR secures local and Codespaces authentication by disabling APISIX header-based authentication unless explicitly enabled.
Changes:
- Disables
ApisixUserMiddlewarein default local and Codespaces environments. - Documents how to enable APISIX/Keycloak authentication explicitly.
| File | Description |
|---|---|
README-keycloak.md |
Documents secure defaults and opt-in configuration. |
env/codespaces.env |
Disables APISIX authentication in Codespaces. |
env/backend.env |
Disables APISIX authentication in local development. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…abled-path case Copilot review feedback on this PR: env/backend.env's new DISABLE_APISIX_USER_MIDDLEWARE=True default is injected into the web container, so the documented `docker compose run --rm web ... pytest main/middleware/apisix_user_test.py` path now runs those tests with the middleware disabled. Confirmed live: on a fresh setup (no personal backend.local.env override), that command now fails 12 tests and errors 6 more with unrelated-looking TypeErrors -- process_request() takes the disabled-path branch (Django's base RemoteUserMiddleware), which touches real request attributes the test mocks don't provide. The existing userinfo_flag_defaults autouse fixture already exists specifically to pin settings regardless of backend.local.env drift (its own docstring says so, for the userinfo create/update flags) -- extended it to also force DISABLE_APISIX_USER_MIDDLEWARE = False, so every test in this file keeps exercising the enabled/trusting code path regardless of the new env default. Added test_disabled_middleware_ignores_forged_header as the requested regression case for the actual disabled path: sets the flag to True and asserts a forged X-Userinfo header is fully ignored through a real request (client.get, not a bare Mock, since the disabled path needs real request attributes) -- the same property already verified live against a real docker compose setup in this PR's earlier commit, now covered at the test level too. Verified via the exact cited repro: `docker compose run --rm web pytest main/middleware/apisix_user_test.py` now passes 19/19 (was 12 failed, 6 errored). Also re-ran via the normal host pytest path (43 passed, unaffected either way). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
alexfigtree
self-requested a review
September 29, 2026 04:03
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What are the relevant tickets?
Fixes https://github.com/mitodl/hq/issues/13471
Description (What does it do?)
ApisixUserMiddlewaredecodes and trusts theX-Userinfoheader (base64-encoded JSON identity claims) unconditionally, with nothing tying it to a real Keycloak login through APISIX -- it just assumes the header could only be there because APISIX put it there.The default local dev profile (
COMPOSE_PROFILES=backend,frontend) never runs APISIX/Keycloak, and Codespaces doesn't either -- while also exposing forwardable, network-reachable ports. In both cases nothing verifies that header before Django trusts it: a request straight to Django with a forgedX-Userinfoheader logs in as (and creates, if the identity doesn't exist) whoever the header claims.DISABLE_APISIX_USER_MIDDLEWAREis already the purpose-built lever for exactly this, already documented inREADME-keycloak.mdas the supported way to run without Keycloak/APISIX. This sets it toTrueby default.How can this be tested?
Verified live, end to end, using the issue's own repro steps:
main:docker compose up(no APISIX/Keycloak profile), then a forgedX-Userinfoheader against/api/v0/users/me/returned"is_authenticated":true,"username":"totally_fake_user"-- a brand-new account, no login required."is_authenticated":false"-- fully rejected, no account created.Also:
RemoteUserMiddlewarebase class +AUTHENTICATION_BACKENDS, which has noremote_user-capable backend) that disabling this middleware is a safe no-op for real Django session-based logins -- doesn't affect ordinary email/password auth at all.env/codespaces.envneeded its own copy of the setting, since it doesn't inherit fromenv/backend.envat all (extendsreplaces a service'senv_filelist rather than merging it).ol-infrastructure, and APISIX is genuinely the only path into Django there (per the issue's own live verification).main/middleware/apisix_user_test.pyandwebsite_content/admin_test.py(42 tests) -- both set this setting explicitly per-test rather than relying on the env default, so unaffected either way, confirmed by actually running them.README-keycloak.md's opt-into-Keycloak instructions to add the now-necessaryDISABLE_APISIX_USER_MIDDLEWARE=Falseoverride, and corrected its now-inaccurate "Keycloak is enabled by default" claim.pre-commit(detect-secrets, trailing-whitespace, end-of-file-fixer) passes on all changed files.Additional Context
None.
🤖 Generated with Claude Code