Django / Correctness and production evidence / Lesson 15
Security settings and trust boundaries
Keep host, origin, transport, input, and permission guarantees separate while applying a reviewed deployment configuration.
On this page
Lesson goals
- Security follows capabilities and data flow
- Validate a redirect destination with the dedicated helper
- Test negative cases at the real boundaries
Security follows capabilities and data flow
List what a caller can control and where each value goes: HTML text, a URL, raw SQL, a file path, a session, or a privileged operation. Each context has its own validation and encoding rules. Django’s normal template escaping is useful for HTML text but cannot validate a redirect destination or authorize a database record. ORM parameters protect ordinary value construction, while arbitrary raw SQL and structural ordering choices require separate care. Avoid one vague sanitize function for every boundary. Keep secrets outside source and client-delivered state, and log only the context needed to investigate a failure without exposing credentials or personal payloads.
Host and CSRF origin settings answer different questions
ALLOWED_HOSTS constrains accepted host names under Django’s host validation; it is not a list of every trusted browser origin or an authorization policy. CSRF_TRUSTED_ORIGINS applies to the CSRF origin-check mechanism and includes scheme-bearing origins under the documented rules. Broad wildcards can erase the intended boundary. HTTPS detection also depends on how a reviewed reverse proxy forwards requests. Only configure trusted forwarded-header interpretation when the proxy strips or controls untrusted client headers as required. A mistaken trust setting can undermine secure redirects and origin checks. Test the actual proxy path rather than copying one deployment recipe blindly.
Validate a redirect destination with the dedicated helper
The first complete example checks three synthetic destinations with url_has_allowed_host_and_scheme. It accepts the approved HTTPS origin and an internal relative path, while rejecting another host. The helper supports a redirect allow policy; it is not an SSRF validator for a server that fetches arbitrary URLs, where DNS, redirects, internal networks, and other capabilities require a stronger design. Do not use a text prefix comparison that can confuse a trusted host with a longer hostile hostname. Authentication views and custom next parameters should preserve a documented destination policy, and tests should include malformed and protocol-relative candidates where the application accepts them.
from django.conf import settings
settings.configure(
SECRET_KEY="synthetic-course-key",
USE_I18N=False, USE_TZ=True,
INSTALLED_APPS=[],
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}},
DEFAULT_AUTO_FIELD="django.db.models.AutoField",
ALLOWED_HOSTS=["testserver"],
)
import django
django.setup()
from django.utils.http import url_has_allowed_host_and_scheme
allowed = {"learn.example.test"}
for destination in ["https://learn.example.test/courses/", "https://other.example.test/", "/courses/"]:
print(url_has_allowed_host_and_scheme(destination, allowed_hosts=allowed, require_https=True))
True
False
True
This local computation validates a redirect policy only. It performs no DNS lookup, request, or navigation.
Make production settings explicit without embedding secrets
The second fragment illustrates a settings boundary with DEBUG disabled, a deliberately narrow host, a secret read from an external environment facility, and secure cookie and redirect intent. It is non-runnable configuration, not a complete deployment or an instruction to set these values on a configured application. The public host is synthetic. HTTPS termination, trusted proxy settings, session storage, static/media serving, error reporting, and recovery must be reviewed together. HSTS and related persistent browser policies need deliberate rollout and rollback analysis. Turning DEBUG off does not make a deployment secure if the rest of those assumptions are wrong.
import os
DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
ALLOWED_HOSTS = ["learn.example.test"]
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Configuration intent: no debug output, explicit host, external secret, and HTTPS-only cookie/redirect policy.
Illustrative production-settings fragment requiring a reviewed deployment and secret source. It deliberately omits an unverified proxy-header trust setting and is not executed.
Test negative cases at the real boundaries
Verify allowed and rejected hosts, expected secure redirects, CSRF failures, scoped resource access, safe template output, and sensitive-field absence in responses. Treat user-uploaded content as untrusted media and serve it under an appropriate storage and content policy rather than as executable application assets. Keep supported Django and dependency patches under review; an older tutorial can reference removed settings or newer-version APIs. Django 5.2 does not gain a later release’s feature merely because current unversioned documentation describes it. Use the versioned references here, and never disable protection globally to make an unexplained request succeed.
Common errors and fixes
ALLOWED_HOSTS is treated as a user permission list
- Why it happens
- Host validation was confused with authorization.
- How to fix it
- Keep host, origin, and resource checks separate.
- Prevent it next time
- Test each boundary independently.
Secure redirects loop behind a proxy
- Why it happens
- HTTPS interpretation does not match the trusted proxy arrangement.
- How to fix it
- Review TLS termination and controlled forwarded headers.
- Prevent it next time
- Validate through the actual isolated deployment path.
A redirect allow check is reused as SSRF protection
- Why it happens
- Browser navigation and server fetch capabilities were conflated.
- How to fix it
- Design the server-fetch boundary separately.
- Prevent it next time
- Document the helper’s limited guarantee.
Check your understanding
1. Why should a trusted proxy header not be enabled merely because a copied guide recommends it?
Show worked solution
Its safety depends on the proxy controlling or stripping untrusted incoming values and on the actual TLS termination path. Verify those properties and the framework setting together. Otherwise client-supplied headers can influence security decisions the application incorrectly treats as trusted transport evidence.
2. What should a rich-text feature do instead of marking every stored string safe?
Show worked solution
Define the supported markup and a reviewed sanitization boundary, retain normal escaping for ordinary text, and test the resulting output contexts. Storage does not make user-authored content trusted. Keep server authorization and data minimization independent of the HTML rendering decision.
Key takeaways
- Security guarantees are context-specific.
- Trust proxy evidence only under a verified arrangement.
- Versioned settings and negative tests matter.