Summary
The new $config->sessionCacheLimiter setting (3.0.258, commit processwire/processwire@ae20ea03) defaults to private_no_expire for guest users. This silently breaks sites using $config->cspNonce with strict-dynamic because it enables HTTP 304 responses that cause a nonce mismatch between the CSP header and the cached HTML body.
The Problem
When a browser caches an HTML page and later revalidates it (via If-None-Match or If-Modified-Since), the server responds with 304 Not Modified — sending new response headers while the browser reuses the cached response body.
For sites using CSP nonces, this means:
- The CSP header contains a new nonce (generated fresh by
$config->cspNonce)
- The HTML body contains
<script nonce="..."> tags with the old nonce (from the cached response)
- The nonces don't match → every nonced inline script is blocked
- With
strict-dynamic, every external script loaded via nonced <script> tags is also blocked
The result is a completely broken page — no JavaScript executes at all.
Reproduction
- Use
$config->cspNonce in a CSP header with strict-dynamic
- Add nonce attributes to
<script> tags: <script nonce="<?=$config->cspNonce?>">
- Leave
sessionCacheLimiter at its default (private_no_expire for guests)
- Visit a page, navigate away, then return — the browser revalidates and gets a 304
- All scripts are blocked; the browser console shows CSP violations for every script on the page
None of the Built-in Options Are Safe for CSP Nonces
| Option |
bfcache |
CSP nonces |
Issue |
nocache |
Broken (no-store) |
Safe |
Disables bfcache |
private_no_expire |
Works |
Broken |
Allows 304s → nonce mismatch |
private |
Works |
Broken |
Sends Last-Modified → enables 304s |
public |
Works |
Broken |
Same 304 problem |
| Custom headers |
Works |
Safe |
Requires user to know the workaround |
Workaround
Use custom headers that force revalidation without enabling conditional requests:
$config->sessionCacheLimiter = [
'guest' => [
'Cache-Control' => 'private, max-age=0, must-revalidate',
],
'loggedin' => 'nocache',
'admin' => 'nocache',
];
Combined with stripping the response validators that enable 304s:
header_remove('ETag');
header_remove('Last-Modified');
This preserves bfcache (no-store is not used) while ensuring the browser always receives a full 200 response with a matching nonce in both the CSP header and the HTML body.
Suggestions
-
Document the incompatibility between private_no_expire/private/public and CSP nonces, so users of $config->cspNonce know to use custom headers.
-
Consider a nonce-aware cache option that automatically strips ETag and Last-Modified when $config->cspNonce is in use, preventing 304 responses while preserving bfcache. For example, an option like 'private_no_revalidate' that sends Cache-Control: private, max-age=0, must-revalidate and removes the conditional request validators.
-
Consider changing the guest default to something safe when CSP nonces are detected — or at minimum, log a warning when private_no_expire is used alongside $config->cspNonce.
Summary
The new
$config->sessionCacheLimitersetting (3.0.258, commit processwire/processwire@ae20ea03) defaults toprivate_no_expirefor guest users. This silently breaks sites using$config->cspNoncewithstrict-dynamicbecause it enables HTTP 304 responses that cause a nonce mismatch between the CSP header and the cached HTML body.The Problem
When a browser caches an HTML page and later revalidates it (via
If-None-MatchorIf-Modified-Since), the server responds with 304 Not Modified — sending new response headers while the browser reuses the cached response body.For sites using CSP nonces, this means:
$config->cspNonce)<script nonce="...">tags with the old nonce (from the cached response)strict-dynamic, every external script loaded via nonced<script>tags is also blockedThe result is a completely broken page — no JavaScript executes at all.
Reproduction
$config->cspNoncein a CSP header withstrict-dynamic<script>tags:<script nonce="<?=$config->cspNonce?>">sessionCacheLimiterat its default (private_no_expirefor guests)None of the Built-in Options Are Safe for CSP Nonces
nocacheno-store)private_no_expireprivateLast-Modified→ enables 304spublicWorkaround
Use custom headers that force revalidation without enabling conditional requests:
Combined with stripping the response validators that enable 304s:
This preserves bfcache (
no-storeis not used) while ensuring the browser always receives a full 200 response with a matching nonce in both the CSP header and the HTML body.Suggestions
Document the incompatibility between
private_no_expire/private/publicand CSP nonces, so users of$config->cspNonceknow to use custom headers.Consider a nonce-aware cache option that automatically strips
ETagandLast-Modifiedwhen$config->cspNonceis in use, preventing 304 responses while preserving bfcache. For example, an option like'private_no_revalidate'that sendsCache-Control: private, max-age=0, must-revalidateand removes the conditional request validators.Consider changing the guest default to something safe when CSP nonces are detected — or at minimum, log a warning when
private_no_expireis used alongside$config->cspNonce.