In Django 6.x, one marketing view embeds a video player that the site-wide SECURE_CSP blocks; how do csp_override and csp_report_only_override behave, and what traps come with them?
answer
- decorators set response attributes
- replace, never merge
- an empty dict disables
- still needs the middleware
basics
~10 scsp_override(config) makes the middleware use config instead of SECURE_CSP for that view's responses; csp_report_only_override does the same for the report-only header. Overrides replace the whole policy, and {} removes the header.
solid answer
~40 sBoth decorators live in `django.views.decorators.csp`. They set an attribute on the view's response (`_csp_config` or `_csp_ro_config`), and `ContentSecurityPolicyMiddleware` uses that dict instead of the setting. The traps: the override **replaces** the global policy rather than adding to it, so `@csp_override({"frame-src": [...]})` sends a policy with only `frame-src` and silently drops `default-src` and `script-src`; I build it as `{**settings.SECURE_CSP, "frame-src": [...]}`. `@csp_override({})` removes the enforced header for that view, and a non-mapping raises `TypeError` at import. The decorators do nothing without the middleware, class-based views need `method_decorator`, and loosening one page loosens the whole origin, so I keep the exception as narrow as the embed.
code
python · 14 linesfrom django.conf import settings
from django.shortcuts import render
from django.utils.csp import CSP
from django.views.decorators.csp import csp_override
LAUNCH_VIDEO_CSP = {
**settings.SECURE_CSP,
"frame-src": [CSP.SELF, "https://player.example.com"],
}
@csp_override(LAUNCH_VIDEO_CSP)
def launch_video(request):
return render(request, "marketing/launch_video.html")go deeper
Recall that csp_override and csp_report_only_override come from django.views.decorators.csp and change the policy for one view.
Explain the mechanism: an attribute on the response read by the middleware, full replacement, and {} meaning no header.
Build overrides from the global dict, keep them narrow, trial them report-only, and remember the middleware requirement and error-page gap.
Govern exceptions: who may loosen the policy per view, how overrides are reviewed, and when a separate origin is the better answer.
## The problem The site policy is strict: `default-src 'self'`, nonce-based `script-src`. One campaign page needs to embed a video player served from another origin in an `<iframe>`. Loosening `SECURE_CSP` for the whole site to fit one page is the wrong trade. Django 6.x provides two per-view decorators in `django.views.decorators.csp`: - `csp_override(config)` for the enforced `Content-Security-Policy` header; - `csp_report_only_override(config)` for the `Content-Security-Policy-Report-Only` header. ## How they work The decorators do not build headers. They wrap the view (sync or async) and set an attribute on the response it returns: `_csp_config` for `csp_override`, `_csp_ro_config` for the report-only variant. On the way out, `ContentSecurityPolicyMiddleware` looks for that attribute and, if present, uses it **instead of** the corresponding setting. Everything else is unchanged: the same `build_policy()` renders the dict, `CSP.NONCE` is still replaced with the request's nonce, and an existing header on the response still wins. ## Replace, never merge This is the trap interviewers look for. The documentation states it directly: applying an override fully replaces the base policy. | Decorator argument | Header sent for this view | |---|---| | `{"frame-src": ["https://player.example.com"]}` | `frame-src https://player.example.com` only; no `default-src`, no `script-src` | | `{**settings.SECURE_CSP, "frame-src": [CSP.SELF, "https://player.example.com"]}` | the site policy plus the extra `frame-src` | | `{}` | no enforced CSP header at all | The first row looks like a small exception but actually removes most protection from the page: anything not governed by `frame-src` is now unrestricted. Build overrides from the global dict and change only what the page needs. ## Other behaviours and traps - **Empty dict disables.** `@csp_override({})` sends no enforced header for the view; `@csp_report_only_override({})` sends no report-only header. Each decorator touches only its own header. - **Type checked early.** Passing anything but a mapping raises `TypeError` when the decorator is applied, which usually means at import time. - **The middleware is required.** Without `ContentSecurityPolicyMiddleware` in `MIDDLEWARE` the attribute is set and never read, so the decorators silently do nothing. - **Class-based views.** Apply them with `django.utils.decorators.method_decorator`, for example on `dispatch`, as with other function decorators. - **Only the view's own responses.** The attribute is set on what the view returns; an error page produced after an exception in the view does not carry it and gets the global policy. - **A header set by hand wins.** If the view sets `response.headers["Content-Security-Policy"]` itself, the middleware leaves it alone, override or not. ## Override or separate origin? An override is the quick fix for one page; it is not the only one. When a page needs a genuinely different trust model, for example it hosts user-generated embeds or a third-party widget with broad requirements, serving it from a separate subdomain with its own policy isolates it from the main origin, so a compromise there cannot reach the main site's pages and cookies scoped to it. That is an architecture decision rather than a decorator, but it is the answer interviewers want to hear when the list of overrides starts to grow. ## Security judgement Django's reference documentation warns that weakening or disabling CSP on any page can compromise the whole site: pages on the same origin can reach each other, so an injection on the loosened page can act on the rest of the site. Practical rules: 1. Add the narrowest source that works: one origin in `frame-src`, not `https:`. 2. Never use `csp_override({})` to fix one blocked resource. 3. Trial the override with `csp_report_only_override` first if the page is new. 4. Keep the list of overridden views short and reviewed; each one is an exception to the site policy.
- How do you apply csp_override to a TemplateView subclass?Use `method_decorator`: `@method_decorator(csp_override(POLICY), name="dispatch")` on the class. The decorator then wraps `dispatch`, sets `_csp_config` on whatever response it returns, and the middleware applies it. The same approach works for `csp_report_only_override`.
- Does a view-level csp_override stop CSP.NONCE from working on that page?No. The override dict is rendered by the same `build_policy()` call with the request's nonce, so `CSP.NONCE` inside the override still becomes `'nonce-<value>'` when the template reads `csp_nonce`. It only disappears if the override omits the placeholder.
saying these in an interview costs you the question
- csp_override adds its directives on top of SECURE_CSP.
- @csp_override({}) restores the site-wide default policy for the view.
- The decorators work even if the CSP middleware is not installed.
- Loosening CSP on one page affects only that page's security.
- csp_override also changes the report-only header.