Added WooCommerce request verification token#1253
Added WooCommerce request verification token#1253vytisbulkevicius merged 3 commits intodevelopmentfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to secure internal WooCommerce report requests by adding a per-request verification token header to outgoing wp_remote_request() calls from the JSON source, intended to be validated server-side to distinguish internal calls from direct public access.
Changes:
- Adds generation/storage of a short-lived token (transient) and sends it via
X-Visualizer-Tokenon detected WooCommerce requests. - Introduces
is_woocommerce_request()helper to identify WooCommerce-related URLs via simple pattern matching.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Check if this is a WooCommerce endpoint request and add verification token. | ||
| if ( $this->is_woocommerce_request( $url ) ) { | ||
| // Generate a unique token for this specific request. | ||
| $token = wp_generate_password( 32, false ); | ||
| set_transient( 'visualizer_wc_token_' . $token, time(), 60 ); | ||
| if ( ! isset( $args['headers'] ) ) { | ||
| $args['headers'] = array(); | ||
| } | ||
| $args['headers']['X-Visualizer-Token'] = $token; | ||
| } |
There was a problem hiding this comment.
New security behavior (token generation/transient storage + conditional header injection) isn’t covered by the existing PHPUnit tests. Add a unit test that stubs HTTP via pre_http_request to assert the header is (or is not) set for WooCommerce URLs, and that the transient lifecycle behaves as expected.
| // Check if this is a WooCommerce endpoint request and add verification token. | ||
| if ( $this->is_woocommerce_request( $url ) ) { | ||
| // Generate a unique token for this specific request. | ||
| $token = wp_generate_password( 32, false ); | ||
| set_transient( 'visualizer_wc_token_' . $token, time(), 60 ); | ||
| if ( ! isset( $args['headers'] ) ) { | ||
| $args['headers'] = array(); | ||
| } | ||
| $args['headers']['X-Visualizer-Token'] = $token; |
There was a problem hiding this comment.
The PR description says the token is validated (e.g., via woocommerce_rest_check_permissions), but in this repo the token is only generated/stored and sent; there is no code that reads X-Visualizer-Token or checks/deletes the corresponding transient. As-is, this doesn’t provide any additional protection and leaves behind short-lived transients on every matched request. Add server-side verification on REST requests (read the header, get_transient()/delete_transient() for single-use, and reject when missing/invalid).
| $args['headers']['X-Visualizer-Token'] = $token; | ||
| } | ||
|
|
||
| do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Connecting to %s with args = %s ', $url, print_r( $args, true ) ), 'debug', __FILE__, __LINE__ ); |
There was a problem hiding this comment.
themeisle_log_event logs print_r( $args, true ). With this change, the log payload will include the newly generated X-Visualizer-Token header, which makes the token retrievable from logs during its validity window. Consider redacting this header (and other sensitive headers) from the logged args or skipping verbose logging for these internal WooCommerce requests.
| do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Connecting to %s with args = %s ', $url, print_r( $args, true ) ), 'debug', __FILE__, __LINE__ ); | |
| // Avoid logging sensitive headers such as X-Visualizer-Token. | |
| $log_args = $args; | |
| if ( isset( $log_args['headers'] ) && is_array( $log_args['headers'] ) ) { | |
| unset( $log_args['headers']['X-Visualizer-Token'] ); | |
| } | |
| do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Connecting to %s with args = %s ', $url, print_r( $log_args, true ) ), 'debug', __FILE__, __LINE__ ); |
| } | ||
|
|
||
| do_action( 'themeisle_log_event', Visualizer_Plugin::NAME, sprintf( 'Connecting to %s with args = %s ', $url, print_r( $args, true ) ), 'debug', __FILE__, __LINE__ ); | ||
| return wp_remote_request( $url, $args ); |
There was a problem hiding this comment.
This code performs a server-side HTTP request to a user-controlled URL using wp_remote_request with no host or protocol restrictions. An authenticated attacker who can reach the JSON import AJAX endpoints (e.g. via visualizer-json-get-roots / visualizer-json-get-data) can supply an arbitrary URL (including internal hosts like http://127.0.0.1/ or cloud metadata IPs), turning this into an SSRF primitive that can probe internal services and, when the response is JSON, exfiltrate data via the chart preview. To mitigate, validate and restrict $_args['url']/$this->_url to an allowlist of expected domains or at minimum block private/reserved IP ranges and non-HTTP(S) schemes before calling wp_remote_request.
|
🎉 This PR is included in version 3.11.15 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
This PR secures internal WooCommerce report requests by sending a custom secret header with
wp_remote_request()and validating it insidewoocommerce_rest_check_permissions, allowing only verified internal AJAX calls while keeping direct public access blocked.Check before Pull Request is ready:
Closes https://github.com/Codeinwp/visualizer-pro/issues/520