Code Review for PR #26 (Wave B)
Reviewer: Claude Code (2026-05-07)
Scope: hypercart-query-guard.php (throttling additions), tests/ActionThrottleTest.php, tests/bootstrap.php (Wave B stubs)
Fix Now
Fix Before Production
Fix Later (Test Infrastructure)
Noted (No Action Required)
- L1.
$_REQUEST['action'] used without nonce verification at line 792 (context detection only). Add a phpcs:ignore comment.
- L2.
as_get_datetime_object() called without function_exists() guard at line 675. Upstream try/catch catches the fatal, but the pattern is fragile.
- L12. Missing test edge cases: empty/edge-case inputs for
defer_count_key, observe-mode deduplication, max-defer safety valve.
Cross-Wave Note
No issues from PR #25 (Wave A) were resolved by this PR. The only cross-wave change was removing a "this is inert until Wave B" comment from the priority registry docblock. All waves are cleanly additive.
Code Review for PR #26 (Wave B)
Reviewer: Claude Code (2026-05-07)
Scope:
hypercart-query-guard.php(throttling additions),tests/ActionThrottleTest.php,tests/bootstrap.php(Wave B stubs)Fix Now
$_SERVER['REQUEST_URI']in log payloads —hypercart-query-guard.php:391,747,944— Raw user input flows into log payloads.wp_json_encodeescapes for JSON, but if logs render in an HTML admin UI this is an XSS vector. Fix: applyesc_url_raw()orsanitize_text_field()before storing. (Fixed in e4dc882)defer_count_key()—hypercart-query-guard.php:706— CRC32 is 32-bit; birthday paradox gives meaningful collisions with thousands of action signatures. Two different (hook, args, group) tuples sharing a counter causes premature "max_defers_reached." Fix: swaphash('crc32', ...)tomd5(...). (Fixed in e4dc882)Fix Before Production
defer_action()cancel/unclaim ordering inverted —hypercart-query-guard.php:641-642—cancel_action()thenunclaim_action()risks an orphaned claim. If AS'scancel_action()already clears the claim (version-dependent), theunclaimcall is redundant. Verify AS behavior; either reverse the order or remove the redundant call. (Fixed in 146e0ed — removed redundant unclaim_action)get_throttle_policy()doesn't validate filter output structure —hypercart-query-guard.php:474-479— Only checksis_array(). A malformed filter return like['elevated' => 'oops']causes a type error downstream whenapply_throttle_value()casts. Fix: apply the same normalization pattern used inget_action_delay_matrix(). (Fixed in 146e0ed)increment_defer_count()—hypercart-query-guard.php:714-718— Read-modify-write race; two concurrent AS runners can lose an increment. Code acknowledges "best-effort." Fix: usewp_cache_incr()+wp_cache_add()fallback for atomicity on persistent caches. (Fixed in 146e0ed)Fix Later (Test Infrastructure)
get_action_throttle_decision(),maybe_defer_action_before_execute(),defer_action(),build_deferred_action_clone()are all untested. Requires stubbingActionScheduler_Store,ActionScheduler_Action, andActionScheduler_SimpleSchedule.$throttle_runtimenot cleared between tests —hypercart-query-guard.php:160— Static array not reset insetUp(). Not an active bug yet (no tests exercise these functions), but will cause test state leakage when integration tests are added.Noted (No Action Required)
$_REQUEST['action']used without nonce verification at line 792 (context detection only). Add aphpcs:ignorecomment.as_get_datetime_object()called withoutfunction_exists()guard at line 675. Upstream try/catch catches the fatal, but the pattern is fragile.defer_count_key, observe-mode deduplication, max-defer safety valve.Cross-Wave Note
No issues from PR #25 (Wave A) were resolved by this PR. The only cross-wave change was removing a "this is inert until Wave B" comment from the priority registry docblock. All waves are cleanly additive.