Skip to content

Remove wildcard ALLOWED_HOSTS default to prevent password reset poisoning - #345

Merged
milesmcc merged 2 commits into
masterfrom
claude/fix-host-header-injection-HAVim
Mar 15, 2026
Merged

Remove wildcard ALLOWED_HOSTS default to prevent password reset poisoning#345
milesmcc merged 2 commits into
masterfrom
claude/fix-host-header-injection-HAVim

Conversation

@milesmcc

@milesmcc milesmcc commented Mar 15, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes a Host header injection vulnerability in the password reset flow. The default ALLOWED_HOSTS = "*" disabled Django's Host header validation, allowing an unauthenticated attacker to poison password reset emails with links to their own domain — capturing valid reset tokens when victims (or email link-preview scanners) click them.

Root cause

django-allauth builds the {{ password_reset_url }} in reset emails using request.build_absolute_uri(), which derives the hostname from the Host header. Django's ALLOWED_HOSTS setting is the security boundary that validates this header — but "*" disables it entirely.

Attack: curl -X POST http://<shynet-ip>/accounts/password/reset/ -H "Host: attacker.com" -d "email=admin@..." → admin receives an email linking to http://attacker.com/accounts/password/reset/key/<valid-token>/.

Fix

Change the default to localhost,127.0.0.1. With ALLOWED_HOSTS properly restricted, Django's CommonMiddleware rejects spoofed Host headers with a 400 before any view runs — making request.get_host() inherently safe downstream.

File Change
settings.py Default *localhost,127.0.0.1. Uses or instead of getenv's default arg so an empty-string env var falls through correctly.
Dockerfile Healthcheck shell fallback matches the new Python default, so it still passes when the env var is unset.
app.json Heroku one-click deploy now requires the user to enter their domain (no default).
kubernetes/secrets_template.yml Removed the * placeholder.
GUIDE.md Changed "consider setting" → "make sure is set", with an explicit warning about *.

Upgrade note

Existing deployments that did not set ALLOWED_HOSTS will need to set it after upgrading. TEMPLATE.env already documented the correct value.

claude added 2 commits March 15, 2026 20:22
The default ALLOWED_HOSTS="*" disabled Django's Host header validation,
allowing an attacker to submit a password reset request with a spoofed
Host header. django-allauth builds the reset URL via request.build_absolute_uri(),
so the email sent to the victim contained a link to the attacker's domain
with a valid reset token. Clicking it (or automated link-preview scanning)
leaked the token to the attacker.

With ALLOWED_HOSTS properly restricted, Django rejects spoofed Host headers
with a 400 before any view runs, making request.get_host() inherently safe.

- settings.py: default to localhost,127.0.0.1 instead of *. Use `or`
  instead of getenv's default arg so an empty-string env var (as shipped
  in the k8s template) falls through correctly.
- Dockerfile: give the healthcheck a matching shell fallback so it still
  passes when the env var is unset.
- app.json: Heroku one-click deploys now default to .herokuapp.com
  (subdomain wildcard) instead of *.
- kubernetes/secrets_template.yml, GUIDE.md: remove the * suggestion
  and document why it's dangerous.
…wildcard

.herokuapp.com is still too broad: anyone can register a Heroku app at
evil.herokuapp.com. Making it required forces the one-click deploy flow
to prompt the user for their actual app domain.
@milesmcc
milesmcc merged commit ca35cab into master Mar 15, 2026
3 checks passed
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.

2 participants