hardening 2.3: 2meet-data-optimizer 1.3.0 (flat-table DDL outside web requests, routed count cache, audit fixes) #7

Merged
wpdev merged 20 commits from hardening/2.3 into main 2026-09-27 18:19:18 +08:00
2 changed files with 29 additions and 11 deletions
Showing only changes of commit 22b46ad175 - Show all commits
+8 -1
View File
@@ -23,6 +23,9 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
class TMDO_REST_API {
/** Blocked views are counted 1 in this many (wpdo_rl_stats). */
private const RL_STATS_SAMPLE = 10;
const NAMESPACE = 'wpdo/v1';
/**
@@ -725,14 +728,18 @@ class TMDO_REST_API {
if ( $cookie_blocked || $ip_blocked ) {
// Track rate-limit hits per post for admin visibility, capped at 100 entries.
// Sampled (1 in RL_STATS_SAMPLE, each sample adding RL_STATS_SAMPLE) so
// anonymous requests cannot turn every blocked view into an option write.
if ( 1 === wp_rand( 1, self::RL_STATS_SAMPLE ) ) {
$rl_stats = get_option( 'wpdo_rl_stats', array() );
$rl_stats[ (string) $post_id ] = ( (int) ( $rl_stats[ (string) $post_id ] ?? 0 ) ) + 1;
$rl_stats[ (string) $post_id ] = ( (int) ( $rl_stats[ (string) $post_id ] ?? 0 ) ) + self::RL_STATS_SAMPLE;
if ( count( $rl_stats ) > 100 ) {
// Keep only the top-100 posts by hit count to bound option size.
arsort( $rl_stats );
$rl_stats = array_slice( $rl_stats, 0, 100, true );
}
update_option( 'wpdo_rl_stats', $rl_stats, false );
}
return new WP_REST_Response(
array(
+15 -4
View File
@@ -377,7 +377,11 @@ class RestApiTest extends TestCase {
$this->assertSame( 'too_many_requests', $response->get_data()['code'] );
}
public function test_post_view_429_increments_rate_limit_stats(): void {
/**
* Blocked requests are counted 1 in 10 (each sample adds 10), so anonymous
* traffic cannot turn every blocked request into an option write.
*/
public function test_post_view_429_samples_rate_limit_stats(): void {
$GLOBALS['_wp_post_types'][40] = 'hp_listing';
$_COOKIE['wpdo_view_40'] = '1'; // Trigger cookie block.
$nonce = wp_create_nonce( 'wp_rest' );
@@ -385,10 +389,17 @@ class RestApiTest extends TestCase {
$req = new WP_REST_Request( 'POST', '/wpdo/v1/listings/40/view' );
$req->set_param( 'id', 40 );
$req->set_header( 'X-WP-Nonce', $nonce );
$this->api->post_view( $req );
$writes = 0;
for ( $i = 0; $i < 200; $i++ ) {
$before = get_option( 'wpdo_rl_stats', [] );
$this->assertSame( 429, $this->api->post_view( $req )->get_status() );
$writes += get_option( 'wpdo_rl_stats', [] ) === $before ? 0 : 1;
}
$stats = get_option( 'wpdo_rl_stats', [] );
$this->assertSame( 1, (int) ( $stats['40'] ?? 0 ) );
$count = (int) ( get_option( 'wpdo_rl_stats', [] )['40'] ?? 0 );
$this->assertSame( 10 * $writes, $count, 'each sample adds 10' );
$this->assertGreaterThan( 0, $writes );
$this->assertLessThan( 60, $writes, 'about 20 writes for 200 blocked requests, not 200' );
}
// ── get_status: rate_limit_stats ─────────────────────────────────────────