SECURE_BROWSER_XSS_FILTER was removed
If you are here because a checklist told you to set SECURE_BROWSER_XSS_FILTER = True:
that setting does not exist. Django removed it in 4.0, and SecurityMiddleware
no longer sends X-XSS-Protection at all.
From Django’s own 4.0 release notes:
SecurityMiddlewareno longer sets theX-XSS-Protectionheader if theSECURE_BROWSER_XSS_FILTERsetting isTrue. The setting is removed.Most modern browsers don’t honor the
X-XSS-ProtectionHTTP header. You can use Content-Security-Policy without allowing'unsafe-inline'scripts instead.
# Delete this. It has done nothing since Django 4.0.# SECURE_BROWSER_XSS_FILTER = TrueDjango will not complain about the leftover setting. Unknown names in a settings module are simply ignored, so the line sits there looking like a control.
curl -sI https://example.com/ | grep -i x-xss-protectionNo output is the correct result.
Why not just send the header anyway
Section titled “Why not just send the header anyway”The header did more than go obsolete — the filter it enabled became a liability. Attackers could craft requests that made the browser’s XSS auditor misidentify legitimate scripts and selectively disable them, turning a defensive feature into a way to break a page’s own defences. Browsers removed the filter rather than fix it.
This is why the modern recommendation is X-XSS-Protection: 0 where the header is
sent at all — explicitly off, not on.
Django’s release notes do show how to set 1; mode=block from custom middleware if
you need to support legacy browsers. Read that as a compatibility escape hatch for a
specific, known requirement, not as a recommendation.
Nothing. Removing the setting changes no behaviour, because the setting has had no behaviour since 4.0. That is the point of the page.
The same conclusion in three other places
Section titled “The same conclusion in three other places”This one is unusually well corroborated, which is worth knowing if you have to argue it with a scanner report:
- Express — helmet sets
X-XSS-Protection: 0by default, explicitly disabling the filter rather than enabling it. - Spring Boot — Spring Security sets it to
0by default. - Browsers removed the filter outright rather than fix it, which is the upstream reason all three frameworks agree.
Three maintained frameworks and the browser vendors, one answer. If a compliance scanner flags the missing header, this is the material to point at.
Related
Section titled “Related”- Content Security Policy — what actually replaced it
- Security headers — the headers Django does still set