fix(rest): 公開端點加上 show_in_rest 欄位 allowlist(A5/A6)

4 個 permission_callback => '__return_true' 的公開端點先前回傳 hot+cold
全部欄位,未註冊 show_in_rest 的私密欄位隨之外洩(A v3.3.1,CVSS 5.3)。

- Schema_Registry::register() 新增 show_in_rest 預設 true
- 新增 get_rest_visible_hot_columns() / get_rest_visible_cold_keys()
- get_listing() 以 array_intersect_key 過濾,保留 id / post_type,
  並提供 wpdo_rest_listing_visible_fields / tmdo_* filter 供調整
- listings_from_zone_a() 同樣以 visible_cols 過濾

unit 379 / integration 398 GREEN

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
2026-07-31 05:21:10 +08:00
parent fa31a0527b
commit d040400da8
2 changed files with 85 additions and 14 deletions
+47 -11
View File
@@ -94,22 +94,24 @@ class TMDO_Schema_Registry {
* - ttl (int) TTL in seconds (Zone B only, null = no expiry).
* - cache_group (string) Object cache group (Zone C only).
* - cache_ttl (int) Cache TTL in seconds (Zone C only).
* - show_in_rest (bool) Whether to expose via GET /wpdo/v1/listings. Default true.
*/
public function register( string $provider, array $config ): void {
$config = wp_parse_args(
$config,
array(
'post_type' => '',
'entity_type' => '',
'meta_key' => '',
'zone' => 'hot',
'data_type' => 'longtext',
'column' => '',
'indexed' => false,
'ttl' => null,
'cache_group' => '',
'cache_ttl' => HOUR_IN_SECONDS,
'provider' => $provider,
'post_type' => '',
'entity_type' => '',
'meta_key' => '',
'zone' => 'hot',
'data_type' => 'longtext',
'column' => '',
'indexed' => false,
'ttl' => null,
'cache_group' => '',
'cache_ttl' => HOUR_IN_SECONDS,
'provider' => $provider,
'show_in_rest' => true,
)
);
@@ -232,6 +234,40 @@ class TMDO_Schema_Registry {
return array_unique( $this->cold_fields[ $post_type ] ?? array() );
}
/**
* Returns hot column names for a post type where show_in_rest is true.
* Used by REST endpoints to restrict publicly exposed fields.
*
* @param string $post_type Post type.
* @return string[] Column names that are REST-visible.
*/
public function get_rest_visible_hot_columns( string $post_type ): array {
$visible = array();
foreach ( $this->fields as $field ) {
if ( 'hot' === $field['zone'] && $field['post_type'] === $post_type && ! empty( $field['show_in_rest'] ) ) {
$visible[] = $field['column'];
}
}
return $visible;
}
/**
* Returns cold meta keys for a post type where show_in_rest is true.
* Used by REST endpoints to restrict publicly exposed fields.
*
* @param string $post_type Post type.
* @return string[] Meta keys that are REST-visible.
*/
public function get_rest_visible_cold_keys( string $post_type ): array {
$visible = array();
foreach ( $this->fields as $field ) {
if ( 'cold' === $field['zone'] && $field['post_type'] === $post_type && ! empty( $field['show_in_rest'] ) ) {
$visible[] = $field['meta_key'];
}
}
return array_unique( $visible );
}
/**
* Get warm field configuration.
*