feat(diagnostics): crypto 健檢 + backup 目錄探測 + 15 個 usermeta 欄位(PR-G)
- Crypto::is_key_derivable()(A v3.3.2):AUTH_KEY / SECURE_AUTH_SALT 皆缺
時回 false,讓金鑰推導優雅短路
- wp tmdo doctor 新增兩項健檢:
· backup 目錄 HTTP 可及性探測(200 = 紅旗並附 nginx 設定建議,
403/404 = OK,0 = 離線時退回檢查 .htaccess)(A v3.1.9)
· crypto 金鑰可推導性(缺常數時警告 notifier secrets 會以明文儲存)
- doctor_callback 呼叫簽章 3 參數 → 1 參數(A v3.0.3 修復)。**ABI 變更**:
AddOn 註冊的 callback 若依賴 rows / full_name 需自行調整
- Member_Fields::register_admin_prefs_group() 由 7 欄擴到 22 欄(A v3.1.4),
補上 wp_user_level / show_welcome_panel / wp_persisted_preferences /
wp_user-settings / community-events-location 等 15 個 WP 原生 usermeta,
這些先前全部落在 wp_usermeta
- capture_before_value 預設 true → false(A v3.1.5):每次受管寫入省一次
DB read。**行為變更**:需要 value_before 的消費者(audit log)要
add_filter( 'wpdo_capture_before_value', '__return_true' ) 明確開啟
unit 451 / 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:
+51
-3
@@ -258,12 +258,13 @@ class TMDO_CLI {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$full_name}`" );
|
||||
|
||||
// Optional doctor_callback.
|
||||
// 傳入 table_suffix + rows + full_name 作為 context;多餘參數對 callable 無害。
|
||||
// Optional doctor_callback. Contract is a single argument: the raw
|
||||
// table suffix as registered (A v3.0.3 fix — passing rows/full_name
|
||||
// broke partner callbacks with a stricter signature).
|
||||
$cb = $cfg['doctor_callback'] ?? null;
|
||||
if ( is_callable( $cb ) ) {
|
||||
try {
|
||||
$result = call_user_func( $cb, $cfg['table_name'], $rows, $full_name );
|
||||
$result = call_user_func( $cb, $tbl_raw );
|
||||
$cb_ok = (bool) ( $result['ok'] ?? true );
|
||||
$cb_msg = (string) ( $result['message'] ?? '' );
|
||||
$status = $cb_ok ? '[OK]' : '[WARN]';
|
||||
@@ -280,6 +281,53 @@ class TMDO_CLI {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Backup directory security check ─────────────────────────────────────
|
||||
$backup_dir = TMDO_Snapshot_Manager::backup_dir();
|
||||
if ( file_exists( $backup_dir ) && is_dir( $backup_dir ) ) {
|
||||
$htaccess_ok = file_exists( $backup_dir . '/.htaccess' );
|
||||
$has_sql_file = (bool) glob( $backup_dir . '/*.sql' );
|
||||
|
||||
// Probe via HTTP — a properly blocked dir returns 403/404; 200 is a red flag.
|
||||
$probe_url = trailingslashit( wp_upload_dir()['baseurl'] ) . TMDO_Snapshot_Manager::BACKUP_DIR_NAME . '/index.php';
|
||||
$response = wp_remote_get(
|
||||
$probe_url,
|
||||
array(
|
||||
'timeout' => 5,
|
||||
'user-agent' => 'TMDO-Doctor/1.0',
|
||||
'sslverify' => false,
|
||||
)
|
||||
);
|
||||
$http_code = is_wp_error( $response ) ? 0 : (int) wp_remote_retrieve_response_code( $response );
|
||||
|
||||
if ( 200 === $http_code ) {
|
||||
WP_CLI::warning( " [WARN] Backup dir is HTTP-accessible ({$probe_url} → 200)." );
|
||||
WP_CLI::warning( ' For nginx, add: location ~* /wpdo-backups/ { deny all; }' );
|
||||
$all_ok = false;
|
||||
} elseif ( in_array( $http_code, array( 403, 404 ), true ) ) {
|
||||
WP_CLI::log( " [OK] Backup dir blocked (HTTP {$http_code})" );
|
||||
} elseif ( 0 === $http_code ) {
|
||||
if ( $htaccess_ok ) {
|
||||
WP_CLI::log( ' [OK] Backup dir has .htaccess deny rule (HTTP probe failed — offline or CLI-only mode)' );
|
||||
} else {
|
||||
WP_CLI::warning( ' [WARN] Backup dir missing .htaccess — run `wp tmdo install` to regenerate.' );
|
||||
$all_ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $has_sql_file && ! $htaccess_ok ) {
|
||||
WP_CLI::warning( ' [WARN] SQL backup files present but .htaccess missing.' );
|
||||
$all_ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Crypto key health check ─────────────────────────────────────────────
|
||||
if ( ! TMDO_Crypto::is_key_derivable() ) {
|
||||
WP_CLI::warning( ' [WARN] AUTH_KEY and SECURE_AUTH_SALT are both absent or empty — TMDO_Crypto cannot derive an encryption key. Notifier secrets will be stored as plaintext. Set these constants in wp-config.php.' );
|
||||
$all_ok = false;
|
||||
} else {
|
||||
WP_CLI::log( ' [OK] Crypto key derivable (AUTH_KEY / SECURE_AUTH_SALT present)' );
|
||||
}
|
||||
|
||||
// Check recent errors.
|
||||
$errors = TMDO_Logger::get_recent( '', 5 );
|
||||
if ( ! empty( $errors ) ) {
|
||||
|
||||
Reference in New Issue
Block a user