Commit 1e451066d8b for woocommerce
commit 1e451066d8bf87e099c3796039fffe66bd16a1c0
Author: Darren Ethier <darren@roughsmootheng.in>
Date: Wed Sep 2 14:06:01 2026 -0400
Reject array wc-auth parameters and replace legacy WPCS suppressions (#68270)
diff --git a/plugins/woocommerce/changelog/fix-woo6-113-auth-callback-suppressions b/plugins/woocommerce/changelog/fix-woo6-113-auth-callback-suppressions
new file mode 100644
index 00000000000..67f9ed23072
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-woo6-113-auth-callback-suppressions
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Reject array values for the wc-auth handshake parameters so they no longer cause a fatal error, and replace legacy WPCS suppression comments in the auth, REST authentication and PayPal IPN handlers with exact PHPCS annotations.
diff --git a/plugins/woocommerce/includes/class-wc-auth.php b/plugins/woocommerce/includes/class-wc-auth.php
index 2f33efebb63..27144bf7ccb 100644
--- a/plugins/woocommerce/includes/class-wc-auth.php
+++ b/plugins/woocommerce/includes/class-wc-auth.php
@@ -167,12 +167,18 @@ class WC_Auth {
);
foreach ( $params as $param ) {
- if ( empty( $_REQUEST[ $param ] ) ) { // WPCS: input var ok, CSRF ok.
+ if ( empty( $_REQUEST[ $param ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Values are required for handshake validation; key creation verifies capability and nonce.
/* translators: %s: parameter */
throw new Exception( sprintf( __( 'Missing parameter %s', 'woocommerce' ), $param ) );
}
- $data[ $param ] = wp_unslash( $_REQUEST[ $param ] ); // WPCS: input var ok, CSRF ok, sanitization ok.
+ // Every handshake parameter is a single string; anything else, an array in particular, is rejected here so that it cannot reach the string-only functions used below.
+ if ( ! is_string( $_REQUEST[ $param ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Values are required for handshake validation; key creation verifies capability and nonce.
+ /* translators: %s: parameter */
+ throw new Exception( sprintf( __( 'Invalid parameter %s', 'woocommerce' ), $param ) ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- The caught message is escaped by esc_html() where it is rendered; escaping here would double-encode translated text.
+ }
+
+ $data[ $param ] = wp_unslash( $_REQUEST[ $param ] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Raw values are needed to validate the submitted URLs and scope; each value is sanitized or escaped where it is used, and key creation verifies capability and nonce.
}
if ( ! in_array( $data['scope'], array( 'read', 'write', 'read_write' ), true ) ) {
@@ -320,7 +326,8 @@ class WC_Auth {
$route = strtolower( wc_clean( $route ) );
$this->make_validation();
- $data = wp_unslash( $_REQUEST ); // WPCS: input var ok, CSRF ok.
+ // Validated by make_validation() above. Values are cleaned or escaped where they are rendered or stored; create_keys() sanitizes what it persists.
+ $data = wp_unslash( $_REQUEST );
// Login endpoint.
if ( 'login' === $route && ! is_user_logged_in() ) {
diff --git a/plugins/woocommerce/includes/class-wc-rest-authentication.php b/plugins/woocommerce/includes/class-wc-rest-authentication.php
index ddc8ac1d82e..07a08d0bf53 100644
--- a/plugins/woocommerce/includes/class-wc-rest-authentication.php
+++ b/plugins/woocommerce/includes/class-wc-rest-authentication.php
@@ -454,7 +454,7 @@ class WC_REST_Authentication {
* @return array|WP_Error
*/
public function get_oauth_parameters() {
- $params = array_merge( $_GET, $_POST ); // WPCS: CSRF ok.
+ $params = array_merge( $_GET, $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.NonceVerification.Missing -- Raw credentials and request data are required for OAuth signature verification.
$params = wp_unslash( $params );
$header = $this->get_authorization_header();
diff --git a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-ipn-handler.php b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-ipn-handler.php
index d92c7ec3fc5..a70d70416e1 100644
--- a/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-ipn-handler.php
+++ b/plugins/woocommerce/includes/gateways/paypal/includes/class-wc-gateway-paypal-ipn-handler.php
@@ -45,8 +45,8 @@ class WC_Gateway_Paypal_IPN_Handler extends WC_Gateway_Paypal_Response {
* Check for PayPal IPN Response.
*/
public function check_response() {
- if ( ! empty( $_POST ) && $this->validate_ipn() ) { // WPCS: CSRF ok.
- $posted = wp_unslash( $_POST ); // WPCS: CSRF ok, input var ok.
+ if ( ! empty( $_POST ) && $this->validate_ipn() ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- PayPal posts the IPN from its own servers, so no site nonce exists; validate_ipn() echoes the payload back to PayPal and only a VERIFIED response allows processing to continue.
+ $posted = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- PayPal posts the IPN from its own servers, so no site nonce exists; the payload is only read after validate_ipn() confirms it, and each value is sanitized where it is consumed.
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
do_action( 'valid-paypal-standard-ipn-request', $posted );
@@ -85,7 +85,7 @@ class WC_Gateway_Paypal_IPN_Handler extends WC_Gateway_Paypal_Response {
WC_Gateway_Paypal::log( 'Checking IPN response is valid' );
// Get received values from post data.
- $validate_ipn = wp_unslash( $_POST ); // WPCS: CSRF ok, input var ok.
+ $validate_ipn = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- The unmodified payload must be echoed back to PayPal for verification, so no site nonce applies and the values cannot be altered here.
$validate_ipn['cmd'] = '_notify-validate';
// Send back post vars to paypal.
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-auth-test.php b/plugins/woocommerce/tests/php/includes/class-wc-auth-test.php
index 5f6907fec3e..96a378e8c53 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-auth-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-auth-test.php
@@ -35,4 +35,40 @@ class WC_Auth_Test extends \WC_Unit_Test_Case {
$maybe_delete_key->setAccessible( true );
$maybe_delete_key->invoke( $wc_auth, $key_data );
}
+
+ /**
+ * Get the protected WC_Auth::make_validation() method.
+ *
+ * @return ReflectionMethod
+ */
+ private function get_make_validation_method() {
+ $make_validation = ( new ReflectionClass( WC_Auth::class ) )->getMethod( 'make_validation' );
+ $make_validation->setAccessible( true );
+
+ return $make_validation;
+ }
+
+ /**
+ * An array submitted for a handshake parameter is reported as invalid rather than missing, and never
+ * reaches a string-only function.
+ */
+ public function test_make_validation_rejects_array_parameters() {
+ $request_backup = $_REQUEST; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Saved and restored so the test can drive make_validation() with a known request.
+ $_REQUEST = array(
+ 'app_name' => 'Test app',
+ 'user_id' => '123',
+ 'return_url' => array( 'https://example.com/return' ),
+ 'callback_url' => 'https://example.com/callback',
+ 'scope' => 'read',
+ );
+
+ $this->expectException( Exception::class );
+ $this->expectExceptionMessage( 'Invalid parameter return_url' );
+
+ try {
+ $this->get_make_validation_method()->invoke( new WC_Auth() );
+ } finally {
+ $_REQUEST = $request_backup;
+ }
+ }
}