fix: NinjaFirewall 相容性 — 保護 WAF 設定選項不被重導向 #2
Reference in New Issue
Block a user
Delete Branch "fix/ninjafirewall-waf-compatibility"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
起因
評估 2meet-data-optimizer 與 NinjaFirewall (WP Edition) 4.9 的相容性,判斷是否需要開發 AddOn。
結論:兩者可共存,不需要 AddOn。 NinjaFirewall 全 codebase 零個
apply_filters('nfw_*')/do_action('nfw_*'),官方相容手段(.htninja、exclude_waf_list、wp-config 常數)全在部署層,AddOn 沒有著力點。但評估過程中找到一個核心必須修補的隱患。
核心變更:
PROTECTED_OPTIONSTMDO_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(lib/firewall.php:573,588)。一旦這些鍵被重導向,WAF 讀不到設定就
nfw_quit()—— 靜默停止防護:不擋任何請求、不報錯、也不寫 log。更糟的是它會定義NFW_STATUS,連帶讓 mu-plugin 的 fallback 也 return,兩層防護同時消失。這不是理論風險:
nfw_rules約 77KB 且autoload=auto,正是 autoload 瘦身工具最想動的目標。修法是在
register_settings_group()開頭以PROTECTED_OPTIONS(nfw_options/nfw_rules/nfw_checked)攔下,並發出_doing_it_wrong()。守衛置於方法開頭而非迴圈內,因此全部鍵都被擋時會提前返回,不再建立空的設定表。autoload 最佳化不受影響 ——
optimize_autoload()只改autoload欄位、不刪列,而 Full WAF 那句SELECT *不看 autoload。其他變更
admin/assets/wpdo-migration-wizard.js)。原本 2 req/s 打同一個 REST endpoint,易觸發 WAF 的 rate-limit 與 bot 偵測。改為與四個 stress-test 面板既有的節奏一致。docs/WAF-COMPATIBILITY.md(新增,279 行):模式差異、symlink 多租戶部署、WP SaaS 開站流程與驗證清單、三條開發約束、實測風險矩陣。tests/unit/OptionsManagerProtectedTest.php(新增):4 tests / 10 assertions。附帶發現:symlink 下的 Full WAF(不在本 PR 範圍)
排查時發現
Cannot retrieve user options from database (#3)的根因與本外掛無關,是部署層問題:Full WAF 用dirname(dirname(dirname(__DIR__)))(firewall.php:78)推導站台位置,而 PHP 的__DIR__會解析 symlink,於是共享 codebase 的多租戶環境中所有站都連到同一個(錯的)資料庫。解法是以
$_SERVER['DOCUMENT_ROOT']為錨點的.htninja(per-site 且不受 symlink 影響)。屬伺服器設定,已在docs/WAF-COMPATIBILITY.md§2 完整記錄,不含在本 PR 的程式碼變更中。Test plan
pre_option_*/pre_update_option_*、全數受保護時不對 DB 發查詢modules/options/、tests/unit/)TMDO_VERSION皆 1.0.2wp tmdo status/doctor不受影響(CLI 豁免)GET /wp-json/wpdo/v1/listings(無 cookie)→ 200,回 JSON 而非 WAF 的 HTML 403 頁?hp_price_min=100&hp_featured=1→ 200/wp-admin/302Review 重點
主要看
modules/options/class-tmdo-options-manager.php的守衛位置 —— 放在方法開頭(過濾 + 早退)而非迴圈內,是為了避免「全部鍵都被擋時仍dbDelta建一張空表」。這也讓單元測試不必依賴dbDeltastub(測試 bootstrap 沒有)。🤖 Generated with Claude Code
https://claude.ai/code/session_01SdjXAU473eekjPBB8vPVRS
評估 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_01SdjXAU473eekjPBB8vPVRSView command line instructions
Checkout
From your project repository, check out a new branch and test the changes.