Should we make it harder to cache responses containing CSP nonces?

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

1 Like

I am known to make mistakes, so I’d definitely be curious to hear back from the openwebdocs team. I know that they are usually pretty quick.

Likewise, I believe the decision goes beyond Django. There should be an industry consensus on how to handle CSP headers. I have reached out to the CSP authors for better clarification in the current CSP spec draft; see also Clarification RFC 7234 caches for present nonce-sources · Issue #815 · w3c/webappsec-csp · GitHub

In both cases, I believe either solution would qualify as a bug and could be applied to 6.0 and 6.1 after the freeze. Which gives us a bit more time for everyone involved to reach consensus.

2 Likes