From 9052bb8fdd518f8a07f7fbdada2d89554a6f08ed Mon Sep 17 00:00:00 2001 From: Robert Staddon Date: Fri, 26 Jun 2026 16:49:09 -0500 Subject: [PATCH] Fix empty ESI nonces on public pages by making the private control login-aware A nonce registered with the `private` control is made private for every visitor: it is emitted with cache-control='private' on the ESI include, and at resolve load_esi_block() re-applies set_private() from the _control URL param with no login check. load_nonce_block() only needs privacy for logged-in users (it calls set_private() only when Router::is_logged_in()), and a logged-out nonce is uid 0 / shared, so it never needed to be private. On a publicly cached page a private fragment has no private vary scope for an anonymous request, so it empties on the server's Private Cache TTL cycle until the next purge. Strip `private` from the nonce control for non-logged-in requests in the wp_create_nonce() override, using the same Router::is_logged_in() check load_nonce_block() uses. Logged-in behavior is unchanged, guest nonces are safely shared via the public ESI path, and _hash validation is unaffected because _control is signed and validated symmetrically. Co-authored-by: Cursor --- litespeed-cache.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/litespeed-cache.php b/litespeed-cache.php index 5a93cdd08..aad157213 100644 --- a/litespeed-cache.php +++ b/litespeed-cache.php @@ -160,6 +160,17 @@ function wp_create_nonce( $action = -1 ) { if ( ! defined( 'LITESPEED_DISABLE_ALL' ) || ! LITESPEED_DISABLE_ALL ) { $control = \LiteSpeed\ESI::cls()->is_nonce_action( $action ); if ( null !== $control ) { + // The `private` cache control on a nonce ESI include only has a private cache + // scope for logged-in visitors. `load_nonce_block()` mirrors this by calling + // `Control::set_private()` only when `Router::is_logged_in()` is true. Forcing + // `private` on the include for guests routes the fragment into the private cache + // with no vary scope, so on the Private Cache TTL cycle it regenerates to an empty + // block on a publicly cached page. Drop `private` for guests so the two stay + // consistent and the nonce resolves through the shared public ESI path. + if ( false !== strpos( $control, 'private' ) && ! \LiteSpeed\Router::is_logged_in() ) { + $control = trim( preg_replace( '/\bprivate\b/', '', $control ) ); + } + $params = array( 'action' => $action, );