From 33ec0b0c73ab8bbe7c00a71a233b46307b34afb6 Mon Sep 17 00:00:00 2001 From: Jason Crist Date: Mon, 25 Aug 2025 14:34:28 -0400 Subject: [PATCH] Fix #38: Add nonce verification to all state-changing operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements comprehensive nonce verification for REST API filter hooks that perform state-changing operations to provide defense-in-depth security. ### Security Enhancements #### Added Nonce Verification to: - **handle_hijack_block_delete()** - Now verifies nonces before deleting posts and pattern files - **handle_block_to_pattern_conversion()** - Verifies nonces before modifying request body content #### Implementation Details - **Consistent Pattern**: Uses same nonce verification approach as existing protected methods - **Proper Error Handling**: Returns standardized WP_Error objects with 403 status codes - **WordPress Standards**: Follows WordPress REST API security best practices - **Defense in Depth**: Adds additional security layer beyond existing authentication ### Changes Made #### Method Security Updates - Enhanced PHPDoc blocks with proper parameter documentation - Added nonce verification using `wp_verify_nonce()` with 'wp_rest' action - Consistent error responses for invalid nonces - Maintains existing functionality while adding security layer #### Benefits - **CSRF Protection**: Prevents cross-site request forgery attacks - **Consistent Security**: All state-changing operations now have nonce verification - **WordPress Compliance**: Aligns with WordPress security best practices - **Audit Trail**: Clear security boundaries for all dangerous operations ### Implementation Notes - Nonce verification occurs before any state changes are made - Returns early with error if nonce is invalid or missing - Preserves existing functionality for valid authenticated requests - No performance impact on read-only operations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- includes/class-pattern-builder-api.php | 32 +++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/includes/class-pattern-builder-api.php b/includes/class-pattern-builder-api.php index 9a90ba4..16b8ac0 100644 --- a/includes/class-pattern-builder-api.php +++ b/includes/class-pattern-builder-api.php @@ -323,9 +323,13 @@ public function register_patterns() { /** - * * Filters delete calls and if the item being deleted is a 'tbell_pattern_block' (theme pattern) * delete the related pattern php file as well. + * + * @param mixed $response The response from the REST API. + * @param WP_REST_Server $server The REST server instance. + * @param WP_REST_Request $request The REST request object. + * @return mixed|WP_Error The response or WP_Error on failure. */ function handle_hijack_block_delete( $response, $server, $request ) { @@ -338,6 +342,16 @@ function handle_hijack_block_delete( $response, $server, $request ) { if ( $post && $post->post_type === 'tbell_pattern_block' && $request->get_method() === 'DELETE' ) { + // Verify nonce for additional security + $nonce = $request->get_header( 'X-WP-Nonce' ); + if ( ! $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { + return new WP_Error( + 'rest_cookie_invalid_nonce', + __( 'Cookie nonce is invalid', 'pattern-builder' ), + array( 'status' => 403 ) + ); + } + $deleted = wp_delete_post( $id, true ); if ( ! $deleted ) { @@ -578,9 +592,25 @@ private function sanitize_pattern_input( $input ) { /** * When anything is saved any wp:block that references a theme pattern is converted to a wp:pattern block instead. + * + * @param mixed $response The response from the REST API. + * @param mixed $handler The handler object. + * @param WP_REST_Request $request The REST request object. + * @return mixed The response, potentially modified. */ public function handle_block_to_pattern_conversion( $response, $handler, $request ) { if ( $request->get_method() === 'PUT' || $request->get_method() === 'POST' ) { + + // Verify nonce for additional security on state-changing operations + $nonce = $request->get_header( 'X-WP-Nonce' ); + if ( ! $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { + return new WP_Error( + 'rest_cookie_invalid_nonce', + __( 'Cookie nonce is invalid', 'pattern-builder' ), + array( 'status' => 403 ) + ); + } + $body = json_decode( $request->get_body(), true ); // Validate JSON decode was successful