Framework protections cover only the paths that use them. Keep authentication, CSRF protection and authorization in the shared pipeline for every route, including new APIs, error handlers and jobs. Turn on the production settings that checks such as Django's check --deploy verify: debug off, HSTS, secure cookies, a Content Security Policy. Trust forwarded headers only from your own proxy, and review each escape hatch, such as CSRF exemptions, raw SQL or HTML and automatic binding, as a security change.
- Defaults protect only the routes, methods and dispatches the framework pipeline actually covers.
- Never authorize only in middleware or the router: CVE-2025-29927 let one request header skip Next.js middleware entirely.
- Exempt a route from CSRF only when it stops accepting ambient cookies and authenticates another way, such as a webhook signature.
- Trust
X-Forwarded-*headers only from your own proxy; otherwise clients choose the scheme, host and address you see. - Raw SQL, raw HTML, automatic binding and debug settings are escape hatches that need review.
- Re-run a request-lifecycle suite when upgrading the framework, the router or the proxy.
| Setting | Value | Note | Source |
|---|---|---|---|
| Django | DEBUG = False, ALLOWED_HOSTS, SECURE_HSTS_SECONDS = 31536000, secure session and CSRF cookies, SECURE_CSP | SECURE_CSP and ContentSecurityPolicyMiddleware need Django 6.0 or later. | Django deployment checklist; Django 6.0 release notes |
| Spring Boot and Spring Security | One SecurityFilterChain ending in anyRequest().authenticated(); server.forward-headers-strategy=native only behind a trusted proxy | Spring Boot 4 renamed server.error.* to spring.web.error.*. | Spring Security reference; Spring Boot application properties |
| ASP.NET Core | An authorization FallbackPolicy that requires an authenticated user; UseHsts, UseHttpsRedirection and UseAntiforgery | — | ASP.NET Core security documentation |
| Express | NODE_ENV=production, app.set('trust proxy', 1) for exactly one proxy, helmet for headers, body size limits | Express has no built-in CSRF protection. | Express production security best practices |
| Ruby on Rails | config.force_ssl = true, config.hosts, a nonce-based content_security_policy, protect_from_forgery | — | Rails security guide |
| Next.js | 12.3.5, 13.5.9, 14.2.25, 15.2.3 or later, with authorization repeated in route handlers and data access | — | GitHub advisory GHSA-f82v-jwr5-mffw (CVE-2025-29927) |
- Run
python manage.py check --deploy --fail-level WARNINGor your framework's equivalent in CI and fix every finding. - Send an internal-only header such as
x-middleware-subrequestfrom outside and confirm protected routes still deny. - Drop the CSRF token while keeping the session cookie on every cookie-authenticated route, including API prefixes.
- Request through the production proxy with a forged
HostandX-Forwarded-Protoand confirm a 400 or the proxy's own values. - Trigger an error with production configuration and confirm no debug page, stack trace or version banner appears.
- Applies to
- Django 6.0 and later for SECURE_CSP; settings tested on 6.0.9 and 6.1.1
- Spring Boot 4.x property names
- Next.js 12.3.5, 13.5.9, 14.2.25, 15.2.3 and later
Framework request pipelines. Use the exact framework/version documentation when selecting APIs or overriding defaults.
- Part of
- Learning path: Browser and API boundaries (step 12 of 18). Workflows: Fix a finding, Secure a feature
Tested fixes: the vulnerable code, the fix, a plausible fix that still fails, and the regression tests that tell them apart.
Learning goals and prerequisites
What you will learn
- Trace framework controls through actual request paths.
- Review raw APIs and exemptions as explicit boundary changes.
- Build regressions that isolate each lifecycle assumption.
Before you start
Developers and tech leads who configure web frameworks and deployments, and reviewers checking that new routes keep the framework's protections.
- Trace middleware ordering, identity establishment, authorization and response serialization.
Refresh first: Enforce authorization for every object action
Go to the guided practiceFrameworks provide useful security mechanisms, but they protect only the paths that use them correctly. An authenticated page may use escaped templates, CSRF middleware and an ORM, while a newly added JSON endpoint bypasses one of those layers. A setting named secure is not an end-to-end property. Trace a request from proxy and router through parsing, session handling, request-intent protection, authorization, database access, serialization and response headers. Identify the exact framework, security modules and deployment mode before copying configuration from another project: the concepts transfer, but configuration names and extension points do not.
The example is a cookie-authenticated profile rename. The intended sequence is authenticate Alice, validate a session-bound CSRF token, authorize Alice's profile, validate the allowed name field, bind the database update and render the new name as text. Each step has a separate purpose. The ORM prevents SQL syntax injection in ordinary parameterized queries; it does not prove Alice owns the selected row. Template escaping protects an output context; it does not validate a URL or authorize a state change.
Middleware-only authorization fails when a request can skip the middleware. In March 2025, CVE-2025-29927 (CVSS 9.1) showed that a request carrying the internal x-middleware-subrequest header could bypass Next.js middleware altogether, so applications whose only authorization check ran there exposed protected pages until they upgraded to 12.3.5, 13.5.9, 14.2.25 or 15.2.3. Keep a check where the data is read, so one skipped layer does not expose it.
A common regression adds an API route with a CSRF exemption because the frontend's first request fails. If the route still accepts a session cookie, the original forgery model remains; the correct repair is to send the framework token through its documented integration, as /domains/appsec/browser-security/csrf-origin-defense/ describes. Make deliberate exceptions only for webhooks or machine clients that use their own authentication contract, such as a provider signature, and never disable a browser control for a whole prefix to accommodate one integration. Spring Security and Django have specific request and token lifecycle behavior, so use the documentation for the deployed version instead of transferring a code fragment between frameworks.
Authentication must precede business authorization, and rejected requests must stop before mutation. Parsing limits should apply before oversized bodies consume excessive work. Method overrides, subrouters and exception handlers can change which middleware executes. Behind a trusted proxy, configure external scheme and host handling so secure-cookie and origin decisions use validated infrastructure metadata. Arbitrary forwarded headers must not redefine the application's origin or turn a plain request into a trusted secure request. Check host validation, secure cookie settings, debug mode and production error responses through the actual reverse proxy.
The settings below are the production security block for a Django 6 project behind a TLS-terminating proxy. They turn off debug output, restrict hosts, redirect to HTTPS, send HSTS for a year, mark the session and CSRF cookies Secure, and use Django 6.0's built-in Content Security Policy with a per-response nonce and 'strict-dynamic'. SECURE_PROXY_SSL_HEADER is safe only when the proxy sets X-Forwarded-Proto itself and removes any value a client sent. In a test project, manage.py check --deploy --fail-level WARNING reported no issues on Django 6.0.9 and 6.1.1, a plain-HTTP request was redirected, an unknown Host received 400, and the page's script nonce matched the one in the Content-Security-Policy header.
# settings_production.py (Django 6.0+): security settings that `manage.py check --deploy` expects.
import os
from django.utils.csp import CSP
DEBUG = False
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] # 50+ random characters, never committed
ALLOWED_HOSTS = ["app.example.test"]
# HTTPS behind a proxy that sets X-Forwarded-Proto and strips it from client requests.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31_536_000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True # only after submitting the domain to the preload list
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_TRUSTED_ORIGINS = ["https://app.example.test"]
# Content Security Policy, built in since Django 6.0; templates use nonce="{{ csp_nonce }}".
SECURE_CSP = {
"default-src": [CSP.SELF],
"script-src": [CSP.NONCE, CSP.STRICT_DYNAMIC],
"object-src": [CSP.NONE],
"base-uri": [CSP.NONE],
"form-action": [CSP.SELF],
"frame-ancestors": [CSP.NONE],
}
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.middleware.csp.ContentSecurityPolicyMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]Each framework has a short list of settings that decide whether its protections apply in production. Check them in the deployed configuration, because an environment variable can re-enable debug output or remove a middleware class that the source tree looks correct about. Each stack page under /fix/, such as /fix/stack/python-django/ or /fix/stack/java-spring/, lists tested fixes and the baseline versions they assume.
| Framework | Production settings | Common bypass to look for |
|---|---|---|
| Django | DEBUG = False, ALLOWED_HOSTS, the SECURE_* settings above, SECURE_CSP (6.0+); manage.py check --deploy | @csrf_exempt on cookie-authenticated views, |safe and mark_safe, raw() with string formatting |
| Spring Boot and Spring Security | One SecurityFilterChain that ends in anyRequest().authenticated(); CSRF left on for browser sessions; server.forward-headers-strategy only behind a trusted proxy | requestMatchers(...).permitAll() broader than intended, csrf.ignoringRequestMatchers on cookie routes, binding requests to entities |
| ASP.NET Core | A FallbackPolicy requiring an authenticated user, UseHsts, UseHttpsRedirection, UseAntiforgery, forwarded headers only from known proxies | [AllowAnonymous] on controllers, [IgnoreAntiforgeryToken], Html.Raw |
| Express | NODE_ENV=production, helmet, express.json({limit}), app.set('trust proxy', 1) for one proxy | No CSRF protection by default, trust proxy set to true behind an unknown chain, auth middleware that catches its own error and calls next() |
| Ruby on Rails | config.force_ssl, config.hosts, content_security_policy with a nonce generator, protect_from_forgery | skip_forgery_protection, permit!, raw and html_safe |
| Next.js | A patched release; authorization in route handlers, server actions and data access, not only in middleware | Middleware-only checks, as in CVE-2025-29927 |
An ORM's raw-SQL API may safely accept separate parameters, but concatenating a sort expression or filter into its source text still grants SQL syntax. A template's raw-HTML helper intentionally disables normal escaping and needs a reviewed sanitizer policy if its input is untrusted. Automatic form or entity binding may make a newly added role field writable unless the operation explicitly selects fields; the mass assignment fix cards at /fix/mass-assignment/ show explicit field selection per framework. These interfaces are useful precisely because they permit behavior outside the default safety boundary.
Review defaults in the actual configuration rather than relying on a framework's marketing or a prior project. Debug mode, allowed hosts, cookie attributes, session storage and dependency patch level all influence deployed behavior. A reverse proxy may generate an error document without the application's headers, or a cache may serve an authenticated response to another user. Keep explicit projections and authorization at the resource path so framework conveniences cannot silently widen the operation when the data model changes. Review background tasks separately: a queued job does not pass through the request middleware that authorized the interactive request, so it must check membership and tenant again when it runs.
- CSRF exemptions on routes that still accept session cookies.
- Raw SQL built by concatenation, even through an ORM's raw-query API.
- Raw HTML helpers such as
|safe,th:utext,Html.Rawandhtml_safeon untrusted values. - Automatic binding of requests to entities,
fields = '__all__'andpermit!. - Custom exception handlers that log a denial and continue.
- Debug flags, admin consoles and actuator or metrics endpoints left reachable in production.
Start with a normal rename that persists exactly one allowed field and renders punctuation correctly. Remove the CSRF token while preserving the cookie, change the profile id while preserving the valid token, supply an extra role field and submit markup as the name. Each variation isolates a different boundary. Check stored values and rendered structure, not just whether the response is successful.
For operational diagnosis, record which layer rejected the request through an internal category and request id. Repeated token failures after a release may reflect middleware ordering or frontend integration; ownership denials may reflect an incorrect route id. Resist resolving both by disabling guards. Maintain a small request-lifecycle suite for representative routes and repeat it when upgrading the framework, changing routers, adding a proxy or replacing serializers. Framework defaults reduce repetitive work when the application's boundaries stay aligned with them.
A framework upgrade can change defaults, middleware behavior, serializer handling or the supported security API. Preserve your application's behavioral contract across that change instead of merely adapting code until it starts. The rename suite provides concrete probes: missing token still denies, foreign id still denies, unknown role still rejects atomically and markup-shaped names remain text. If a test changes, explain whether the framework behavior changed or the application's policy intentionally changed.
Dependencies around the framework matter too. A JSON body parser may accept shapes the handler did not expect; a session backend outage may affect authentication; a proxy may alter origin metadata; a template plugin may reinterpret strings. Trace each integration at its own interface and include failure behavior. A fallback authentication path should never turn a session-store outage into an anonymous user with default administrative permission. Pin the supported framework version range, track its security releases and retire unsupported branches with an explicit migration plan.
Deployment validation should inspect effective configuration. An environment variable can enable debug output or disable a middleware class despite reviewed source settings. Test the packaged application through the actual proxy and routing arrangement using synthetic data. A local unit test of a view function is valuable, but does not demonstrate that deployed middleware runs in the same order or that the error response avoids a debug page. Keep those verification scopes distinct in the release record so defaults remain useful, observable guarantees rather than assumptions.
Guided practice
Audit a supplied cookie-backed rename flow with complete policies.
Supplied scenario material
Alice session=A1, token=TA, profileId=A. Bob profileId=B. Names initially Alice and Bob. Role for Alice=member.
Rename input schema: profileId from route; body contains name only, 1..80 characters. Unknown fields reject with 400 atomically. Missing token -> 403; wrong owner -> 404; missing session -> 401.
Normal output uses escaped text.
R1: A1,TA,/profiles/A,{name:Ada & Lin}.
R2: A1,no token,/profiles/A,{name:Changed}.
R3: A1,TA,/profiles/B,{name:Changed}.
R4: A1,TA,/profiles/A,{name:Changed,role:admin}.
R5: A1,TA,/profiles/A,{name:<strong>Ada</strong>}.
Regression patch: API subrouter exempts all JSON endpoints from CSRF but still accepts A1 cookies.1. Trace the normal lifecycle
Evaluate R1 and identify what authentication, authorization, binding and rendering each guarantee.
Expected result and reasoning
R1 succeeds and persists
Ada & Linfor A only. Authentication establishes Alice; ownership chooses the permitted row; binding preserves the value as data; escaped rendering shows the ampersand correctly.2. Isolate missing and wrong authority
Evaluate R2 and R3 against the complete middleware sequence.
Expected result and reasoning
R2 returns 403 with Alice unchanged. R3 returns 404 with Bob unchanged. A valid CSRF token does not authorize another profile, and valid ownership does not replace CSRF protection.
3. Inspect binding and output escape hatches
Evaluate R4/R5 and state the stored versus rendered representation.
Expected result and reasoning
R4 rejects with 400 and no partial name or role change. R5 stores the literal string
<strong>Ada</strong>if names permit those characters and displays it as text, creating no strong element.4. Assess the exemption patch
Explain why JSON content alone does not justify the broad exemption and name the concrete route regression.
Expected result and reasoning
The route still relies on ambient cookies. Its complete origin/content-type/CSRF design must be justified; merely declaring JSON is not a replacement. Repeating R2 on the API route must not mutate A, and supported frontend requests must send the documented token.
Paper exercise: sessions, tokens, routes and policies are supplied. The express-api project below runs the same lifecycle checks against a real Express API.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Run a local project
Download and unzip a starter with failing tests and a step-by-step README, edit the named exercise file, and run the tests until they pass. The reference solution is a separate download, so you can attempt the repair before you see it.
Secure a real HTTP API
Run Express requests with real signed tokens. Repair invoice authorization and stop user-controlled properties from changing roles or tenants.
Needs: Internet access for npm ci, which downloads the pinned packages; the tests run on localhost only.
In the extracted folder, run:
npm ci --ignore-scripts
npm test14 regression tests · The starter intentionally fails some cases.
Opens in a dev container (VS Code Dev Containers or GitHub Codespaces) with a pinned image, and includes Semgrep rules with their own tests in semgrep/.
Part of the learning path Developer foundations
Localhost tests with synthetic identities and in-memory records; includes actual JOSE verification.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- Spring Security CSRF integration
- Django: security in Django. Django 6.1.
- Django: deployment checklist. Django 6.1.
- Django 6.0 release notes: Content Security Policy support
- GitHub advisory GHSA-f82v-jwr5-mffw: Next.js middleware authorization bypass
- Express: production security best practices
- Ruby on Rails security guide
Reference information
- SDLC phase
- Development
- ASVS 5.0.0 requirements
- v5.0.0-13.4.2 · v5.0.0-3.3.1 · v5.0.0-3.4.1 · v5.0.0-3.4.3 · v5.0.0-15.3.4