The CSP nonce attribute on a request is lazy, so we know whether or not it was used. If it was used in a response, should we make it harder to accidentally cache that response? Perhaps by:
- adding the nonce to the cache key for Django’s per-site and per-view caches (to frustrate caching but perhaps allow strategies that use placeholders to still work)
- or, setting
Cache-Control: private
We link to MDN’s advice to avoid caching responses containing nonce values:
However, because nonces must be unique per request, extra care is needed when using full-page caching (e.g., Django’s cache middleware, CDN caching). Serving cached responses with previously generated nonces may result in reuse across users and requests. Although such responses may still appear to work (since the nonce in the CSP header and HTML content match), reuse defeats the purpose of the nonce and weakens security.
Content Security Policy | Django documentation | Django
This would be equivalent to the situation for CSRF (forum post), where you simply get no caching, because we vary on cookies when setting the CSRF cookie.
Our advice continues:
- If caching is necessary, use a strategy that injects a fresh nonce on each request…
So, having not given this all that much thought, my guess is that that would look like:
MIDDLEWARE = [
ContentSecurityPolicyMiddleware,
RewriteNonceMiddleware,
UpdateCacheMiddleware, # usually goes first
FetchFromCacheMiddleware,
]
RewriteNonceMiddleware(...
def process_request():
""""swap out the secure nonce for a placeholder nonce"""
def process_response():
"""parse response (!!) and rewrite body with the secure one"""
In which case, even under my proposal, when UpdateCacheMiddleware runs, it only sees the placeholder nonce and caches that in a reusable way. (Now, how realistic is this?)
This came up in review with @nessita and @codingjoe, see also this ticket @codingjoe opened with MDN for clarifying guidance around whether Cache-Control: Private is a good defensive measure here.
/cc @robhudson