Repository navigation
feat(core): redirect GET and HEAD for legacy host names - #213
Merged
Merged
Conversation
When the site moves to a new host name while an old one still reaches it, links and bookmarks that name the old host should land on the new one. QUEUEBOARD_LEGACY_HOSTS lists the old names; a GET or HEAD request whose Host is one of them is redirected to the same path and query on QUEUEBOARD_BASE_URL's scheme and host. A 302 by default; QUEUEBOARD_LEGACY_HOST_REDIRECT_PERMANENT makes it a 301 once the redirect has proven itself. Every other method is served as before: webhook senders don't follow redirects, and a redirected POST (an analytics beacon) loses its body. The target comes only from QUEUEBOARD_BASE_URL, never from the request, so it can't be an open redirect. The middleware removes itself when no legacy host is set, and, with a warning at startup, when the base URL is unset, not http(s), or names a legacy host. It is first in MIDDLEWARE, so a redirected request reaches nothing that could redirect it again, set a cookie, or touch the database. Production settings now place WhiteNoise directly after SecurityMiddleware by lookup rather than by index, so it stays where WhiteNoise's docs ask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoXLf4KSfqzMFrdZGQG9Up
- Warn at startup about each QUEUEBOARD_LEGACY_HOSTS entry that isn't a bare host name (a URL, a wildcard, a leading-dot domain) instead of dropping it silently, as .env.example promised. Valid entries beside it still redirect. - Docs: other methods are never moved by the redirect, so webhook senders, monitors and anything that posts must be moved to the new name themselves; and the proxy must pass each request's own Host through, or the new name redirects to itself. - The CI settings pin QUEUEBOARD_LEGACY_HOSTS empty, so a developer's .env can't turn the redirect on for the suite. - The stack tests' docstring no longer implies they pin the order; MiddlewareOrderTests does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QoXLf4KSfqzMFrdZGQG9Up
bryangingechen
marked this pull request as ready for review
October 11, 2026 22:09
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.
Motivation
When the site moves to a new host name while an old one still reaches the same deployment, links and bookmarks that name the old host should land on the new one, without breaking anything that can't follow a redirect.
What changes
QUEUEBOARD_LEGACY_HOSTS(comma-separated; empty means off). AGETorHEADrequest whoseHostis one of them gets a redirect to the same path and query onQUEUEBOARD_BASE_URL's scheme and host. Matching ignores case, a port and a trailing dot.QUEUEBOARD_LEGACY_HOST_REDIRECT_PERMANENT: the redirect is a 302 by default; set this to make it a 301 once it has worked for a while.localhostfor a container health check, and so on.QUEUEBOARD_BASE_URL, never from the request: not from the path, and not fromX-Forwarded-Host. The middleware removes itself (MiddlewareNotUsed) when no legacy host is set. When the base URL is unset, not http(s), or names a legacy host (which would redirect to itself), it also removes itself and logs a warning at startup. An entry that isn't a bare host name (a URL,*,.example.org) is ignored with a warning of its own.MIDDLEWARE, ahead ofSecurityMiddleware, so a redirected request reaches nothing that could redirect it again, set a cookie, or touch the database.production.pyused to insert WhiteNoise at a fixed index. It now places WhiteNoise directly afterSecurityMiddlewareby looking that up, so the order stays the one WhiteNoise's docs ask for.Hostheader rather thanrequest.get_host(). The host only decides whether to redirect, andget_host()would reject a host missing fromALLOWED_HOSTSbefore that. Requests that aren't redirected still go throughALLOWED_HOSTSas before. The docs say to keep the old names inDJANGO_ALLOWED_HOSTSwhile they still reach the site, so that the methods that aren't redirected keep being served.base.pyand.env.example.docs/production_image.mdhas a paragraph on moving to a new name, andqb_site/AGENTS.mdnotes the middleware's position.User-visible effects
None until
QUEUEBOARD_LEGACY_HOSTSis set. No migrations.Rollout
Authorizationheader when it is redirected to another host.QUEUEBOARD_LEGACY_HOSTSto the old name. Check thatcurl -sI https://<old>/api/gives302withLocation: https://<new>/api/, that aPOSTto the old name is still served, and thathttps://<new>/api/still gives200. A proxy that rewrote the new name'sHostto an old one would make the new name redirect to itself.Webhook senders, monitors and anything else that posts are never moved by the redirect: move them to the new name before the old one goes away.
QUEUEBOARD_LEGACY_HOST_REDIRECT_PERMANENT=1.Testing
core.tests.test_legacy_host_redirect(20 tests,SimpleTestCase, so any database query fails them)://evil…path and a forgedX-Forwarded-Hostchange only the path, never the target host.MIDDLEWARE, and in production settings first, thenSecurityMiddleware, then WhiteNoise.localhostgets 200, and with the base URL unset the warning is logged and nothing redirects.uv run ruff check .,uv run ruff format --check .%0d%0ais encoded),//paths, http → https in one hop, and that CORS is unaffected. The review fixes are inc6e7c350.bash scripts/repo_check_compose.sh: all 13 steps passed locally (core 133, syncer 543, analyzer 606, api 28, zulip_bot 350, console 96, site_analytics 81 tests).🤖 Generated with Claude Code
https://claude.ai/code/session_01QoXLf4KSfqzMFrdZGQG9Up