336664473b
Anti-EAV Lint + Quality Gate / anti-eav-lint (pull_request) Successful in 15s
Tests / PHP Lint (pull_request) Successful in 17s
Tests / Unit Tests (pull_request) Successful in 51s
Tests / Integration Tests (pull_request) Successful in 1m22s
Tests / PHPCS (pull_request) Successful in 1m12s
Tests / PHPStan (pull_request) Successful in 1m10s
- show_in_rest defaulted to true, so GET /wpdo/v1/listings and /listings/{id} (public by design) returned every registered hot / cold field to anonymous visitors — e.g. infocards tmeetic_ig_access_token_enc / _token_expiry / _user_id and WooCommerce _purchase_note (confirmed on dev36)
- Fields are private unless registered with show_in_rest => true; protected (underscore) and credential-like keys (token, secret, password, api key, private key, credential, salt, nonce) are never exposed
- Zone A list path no longer returns whole rows when nothing is public, and filters / orderby accept public columns only
- Version 1.1.1; CHANGELOG, readme
- Verify: unit 690 (+3), integration 525 (+3 private-column tests, RED on 1.1.0), live suite rest 2/2 (RED on 1.1.0), all live 49/49, phpcs 0, stan OK
77 lines
2.8 KiB
PHP
77 lines
2.8 KiB
PHP
<?php
|
|
/**
|
|
* Suite: the public listings endpoints never return private fields to anonymous users.
|
|
*
|
|
* Bug (dev36, 2026-09-26, fixed in 1.1.1): GET /wpdo/v1/listings/{id} is public and
|
|
* returned every registered hot / cold field, because show_in_rest defaulted to true —
|
|
* e.g. the infocards IG token fields on hp_vendor and WooCommerce's _purchase_note.
|
|
*
|
|
* @package WP_Data_Optimizer
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* GET a wpdo REST route as an anonymous visitor.
|
|
*
|
|
* @param string $route Route, e.g. /wpdo/v1/listings/12.
|
|
* @param array<string, mixed> $params Query parameters.
|
|
* @return array<int|string, mixed> Response data.
|
|
*/
|
|
function tmdo_live_rest_anonymous( string $route, array $params = array() ): array {
|
|
$user = get_current_user_id();
|
|
$request = new WP_REST_Request( 'GET', $route );
|
|
$request->set_query_params( $params );
|
|
wp_set_current_user( 0 );
|
|
try {
|
|
$response = rest_do_request( $request );
|
|
} finally {
|
|
wp_set_current_user( $user );
|
|
}
|
|
tmdo_live_assert_same( 200, $response->get_status(), "GET {$route}" );
|
|
return (array) $response->get_data();
|
|
}
|
|
|
|
/**
|
|
* Keys that must never reach an anonymous visitor.
|
|
*
|
|
* @return string[]
|
|
*/
|
|
function tmdo_live_rest_private_keys(): array {
|
|
return array( 'tmeetic_ig_access_token_enc', 'tmeetic_ig_token_expiry', 'tmeetic_ig_user_id', '_purchase_note' );
|
|
}
|
|
|
|
TMDO_Live::test(
|
|
'GET /wpdo/v1/listings/{id} returns no private field to an anonymous visitor',
|
|
static function () {
|
|
$checked = 0;
|
|
foreach ( array( 'hp_vendor', 'product' ) as $post_type ) {
|
|
$ids = get_posts( array( 'post_type' => $post_type, 'post_status' => 'publish', 'numberposts' => 1, 'fields' => 'ids' ) );
|
|
if ( ! $ids ) {
|
|
continue;
|
|
}
|
|
$data = tmdo_live_rest_anonymous( '/wpdo/v1/listings/' . (int) $ids[0] );
|
|
tmdo_live_assert_same( array(), array_values( array_intersect( tmdo_live_rest_private_keys(), array_keys( $data ) ) ), "{$post_type} #{$ids[0]} private keys" );
|
|
tmdo_live_assert_same( array(), array_values( preg_grep( '/^_|token|secret|passw|api_?key/i', array_keys( $data ) ) ), "{$post_type} #{$ids[0]} protected / credential-like keys" );
|
|
++$checked;
|
|
}
|
|
if ( 0 === $checked ) {
|
|
throw new TMDO_Live_Skip( 'no published hp_vendor or product on this site' );
|
|
}
|
|
}
|
|
);
|
|
|
|
TMDO_Live::test(
|
|
'GET /wpdo/v1/listings returns no private field to an anonymous visitor',
|
|
static function () {
|
|
foreach ( array( 'hp_vendor', 'product', 'hp_listing' ) as $post_type ) {
|
|
if ( ! post_type_exists( $post_type ) ) {
|
|
continue;
|
|
}
|
|
foreach ( tmdo_live_rest_anonymous( '/wpdo/v1/listings', array( 'post_type' => $post_type, 'per_page' => 5 ) ) as $item ) {
|
|
tmdo_live_assert_same( array(), array_values( preg_grep( '/^_|token|secret|passw|api_?key/i', array_keys( (array) $item ) ) ), "{$post_type} list item keys" );
|
|
}
|
|
}
|
|
}
|
|
);
|