feat(security): DDL 型別白名單 + CLI 表名守衛(A7/A8)

- TMDO_Installer::ALLOWED_COL_BASE_TYPES(19 型別)+ validate_col_type(),
  在 ALTER TABLE ADD COLUMN 與 CREATE TABLE 組欄位前驗證:先前 partner
  plugin 經 Schema Registry 提供的 $col_type 直接拼進 DDL。
- TMDO_CLI::assert_safe_table_name(),守 doctor 的 SHOW COLUMNS / PRAGMA
  與 benchmark 的 COUNT(*) 三處無法 prepare 的識別字。

對應 A v3.3.4。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:18:55 +08:00
parent a0847a27bf
commit fa31a0527b
2 changed files with 62 additions and 0 deletions
+18
View File
@@ -162,6 +162,7 @@ class TMDO_CLI {
// v2.1.2 doctor column-drift check: compare DB columns vs Schema_Registry declared columns.
$declared = array_keys( $registry->get_hot_columns( $pt ) );
$existing = array();
self::assert_safe_table_name( $t );
if ( TMDO_IS_MYSQL ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
$rows = $wpdb->get_col( "SHOW COLUMNS FROM `{$t}`" );
@@ -914,6 +915,7 @@ class TMDO_CLI {
continue;
}
self::assert_safe_table_name( $full_table );
$total_rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$full_table}`" ); // phpcs:ignore WordPress.DB
// Use partner-supplied callback when available.
@@ -1318,6 +1320,22 @@ class TMDO_CLI {
// ── Private helpers ───────────────────────────────────────────────────
/**
* Reject table names that are not plain identifiers.
*
* Guards raw SQL that cannot use $wpdb->prepare() for table identifiers
* (SHOW COLUMNS, PRAGMA, COUNT(*) probes). All callers derive $table_name
* from TMDO_DB::table() or Custom_Table_Registry — developer-controlled,
* not from HTTP input — but this check prevents breakage if that ever changes.
*
* @param string $table_name Fully-qualified table name to validate.
*/
private static function assert_safe_table_name( string $table_name ): void {
if ( ! preg_match( '/^[a-zA-Z0-9_]+$/', $table_name ) ) {
WP_CLI::error( "Unsafe table name rejected: '{$table_name}'" );
}
}
/**
* Format the HPCT status string for display.
*