9fa84845be
評估 NinjaFirewall (WP Edition) 4.9 的相容性,結論是兩者可共存且
不需要開發 AddOn(該外掛全 codebase 零個 apply_filters('nfw_*') /
do_action('nfw_*'),官方相容手段全在部署層)。但核心有一個隱患必須補。
TMDO_Options_Manager::register_settings_group() 以 pre_update_option_{key}
回傳 $old_value,讓選項不再落地 wp_options、改存專屬設定表。而
NinjaFirewall 的 Full WAF 走 auto_prepend_file,在 WordPress 載入前就以
原生 mysqli 直查 wp_options 取 nfw_options / nfw_rules。一旦這些鍵被
重導向,WAF 會讀不到設定而靜默停止防護 —— 不報錯、不寫 log。
nfw_rules 約 77KB 且 autoload=auto,正是 autoload 瘦身最誘人的目標,
因此這條路徑並非理論風險。
Added
- PROTECTED_OPTIONS 常數與註冊守衛(nfw_options / nfw_rules / nfw_checked),
命中時發出 _doing_it_wrong()。守衛置於方法開頭,全部鍵都被擋時提前返回,
不再建立空的設定表。
- tests/unit/OptionsManagerProtectedTest.php(4 tests / 10 assertions),
鎖住「受保護鍵絕不會被掛上 pre_option_* / pre_update_option_* 攔截」。
- docs/WAF-COMPATIBILITY.md:模式差異、symlink 多租戶部署、WP SaaS 開站
流程與驗證清單、三條開發約束、實測風險矩陣。
Changed
- Migration Wizard 輪詢 500ms → 2s,與四個 stress-test 面板一致。
原本 2 req/s 打同一 REST endpoint,易觸發 WAF rate-limit 與 bot 偵測。
autoload 最佳化不受影響:optimize_autoload() 只改 autoload 欄位、不刪列,
而 Full WAF 的 SELECT * 不看 autoload。
驗證:591 tests / 1166 assertions 通過,PHPCS 零違規,版本一致性 1.0.2。
dev30 於 Full WAF 與 WP WAF 兩種模式下實測,firewall log 中 TMDO 相關
攔截 0 筆。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdjXAU473eekjPBB8vPVRS
101 lines
3.4 KiB
PHP
101 lines
3.4 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
use PHPUnit\Framework\TestCase;
|
||
|
||
/**
|
||
* Unit tests for TMDO_Options_Manager 的受保護選項守衛。
|
||
*
|
||
* 背景:register_settings_group() 會以 pre_update_option_{key} 回傳 $old_value,
|
||
* 讓選項不再落地 wp_options、改存專屬設定表。但 NinjaFirewall Full WAF 走
|
||
* auto_prepend_file,在 WordPress 載入前就以原生 mysqli 直查 wp_options 取
|
||
* nfw_options / nfw_rules。若這些鍵被重導向,WAF 會讀不到設定而靜默停止防護
|
||
* (不報錯、不寫 log),因此必須在註冊階段就擋下。
|
||
*
|
||
* 註:混合「受保護 + 一般」鍵的情境需要走到 dbDelta(),而單元測試 bootstrap
|
||
* 沒有該 stub(ABSPATH 為假路徑),故此處只覆蓋純受保護鍵與常數契約 —— 這已
|
||
* 足以鎖住「受保護鍵絕不會被掛上攔截 filter」這個核心保證。
|
||
*/
|
||
class OptionsManagerProtectedTest extends TestCase {
|
||
|
||
/** 每個測試前清空靜態註冊表,避免跨測試污染。 */
|
||
protected function setUp(): void {
|
||
parent::setUp();
|
||
|
||
$prop = new ReflectionProperty( 'TMDO_Options_Manager', 'redirected_options' );
|
||
$prop->setAccessible( true );
|
||
$prop->setValue( null, array() );
|
||
}
|
||
|
||
// ── 常數契約 ────────────────────────────────────────────────────────────
|
||
|
||
public function test_protected_options_covers_ninjafirewall_keys(): void {
|
||
$ref = new ReflectionClass( 'TMDO_Options_Manager' );
|
||
$protected = $ref->getConstant( 'PROTECTED_OPTIONS' );
|
||
|
||
$this->assertIsArray( $protected );
|
||
foreach ( array( 'nfw_options', 'nfw_rules', 'nfw_checked' ) as $key ) {
|
||
$this->assertContains(
|
||
$key,
|
||
$protected,
|
||
"{$key} 必須列入 PROTECTED_OPTIONS,否則重導向後 NinjaFirewall Full WAF 會靜默失效"
|
||
);
|
||
}
|
||
}
|
||
|
||
// ── 註冊守衛 ────────────────────────────────────────────────────────────
|
||
|
||
public function test_protected_options_are_not_registered(): void {
|
||
TMDO_Options_Manager::register_settings_group(
|
||
'waf_guard_test',
|
||
array( 'nfw_options', 'nfw_rules', 'nfw_checked' )
|
||
);
|
||
|
||
$this->assertSame(
|
||
array(),
|
||
TMDO_Options_Manager::get_redirected_options(),
|
||
'受保護的選項不得進入重導向清單'
|
||
);
|
||
}
|
||
|
||
public function test_protected_options_get_no_interception_filters(): void {
|
||
TMDO_Options_Manager::register_settings_group(
|
||
'waf_guard_test',
|
||
array( 'nfw_options', 'nfw_rules' )
|
||
);
|
||
|
||
$hooks = $GLOBALS['_wp_filter_callbacks'] ?? array();
|
||
|
||
foreach ( array( 'nfw_options', 'nfw_rules' ) as $key ) {
|
||
$this->assertArrayNotHasKey(
|
||
"pre_option_{$key}",
|
||
$hooks,
|
||
"pre_option_{$key} 不得被掛上 —— 會讓 WAF 讀到轉址後的值"
|
||
);
|
||
$this->assertArrayNotHasKey(
|
||
"pre_update_option_{$key}",
|
||
$hooks,
|
||
"pre_update_option_{$key} 不得被掛上 —— 會讓設定不再寫回 wp_options"
|
||
);
|
||
}
|
||
}
|
||
|
||
/** 全部鍵都被擋下時應提前返回,不建立設定表。 */
|
||
public function test_all_protected_keys_skips_table_creation(): void {
|
||
global $wpdb;
|
||
|
||
$before = $wpdb->last_query ?? null;
|
||
|
||
TMDO_Options_Manager::register_settings_group(
|
||
'waf_guard_test',
|
||
array( 'nfw_options' )
|
||
);
|
||
|
||
$this->assertSame(
|
||
$before,
|
||
$wpdb->last_query ?? null,
|
||
'不應對資料庫發出任何查詢'
|
||
);
|
||
}
|
||
}
|