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

2 Likes

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.

3 Likes

Replying to your latest message on the MDN issue:

So I would go with a private policy for the cache middleware while emitting a UserWarning during runtime, or a separate check (doesn’t cache per view policies). However, the CSP middleware should have an option to disable the injected private CC policy for people that use a CDN that supports regeneration.

private by default (that is, if the nonce is materialized) with a way to opt out sounds good at first blush. @robhudson what do you think?

What would the UserWarning be for? (We don’t do that for CSRF. You may be interested in this documentation ticket.)

1 Like

I opened a ticket and flagged it as a candidate for upcoming sprints.

1 Like