WordPress.org 外掛提交技能
讓任何外掛通過 wp.org 的自動掃描和人工審查,無需重新上傳五次。濃縮自真實的審查週期(那種一個小小的 .gitkeep 就能讓整個掃描失敗的情況)。
維護者: moksa · moksaweb.com · 授權:MIT
使用時機
當使用者要求以下事項時觸發此技能:
- 為 wordpress.org 打包/建置一個乾淨的安裝 zip
- 提交或重新提交外掛到 wp.org 目錄
- 修正 wp.org 審查團隊的回饋(4 個經典類別)
- 診斷自動掃描拒絕原因(隱藏檔案、前綴、資料庫呼叫…)
- 一致地升級外掛版本(檔案頭 + readme + 常數)
- 在上傳前預檢外掛
請勿用於:
- 非 WordPress 專案
- 永遠不會上傳到 wp.org 的私用/內部外掛(審查規則不適用,不過安全實務仍有幫助)
兩道關卡
- 自動掃描(Plugin Check,上傳時執行)— 硬性封鎖。必須通過才能送到人類審查員手上。請參閱
references/automated-scan.md。 - 人工審查(通過掃描後由真人進行)— 會 email 告訴你問題,你修正後回覆。幾乎總是 4 個程式碼類別之一(
references/review-categories.md)加上 lint 工具抓不到的政策規範 — 授權、遠端程式碼、追蹤、後台干擾、商標、外部服務揭露(references/guidelines.md)。
參考文件對應
| 主題 | 檔案 |
|---|---|
| Plugin Check (PCP) 操作手冊 — UI/CLI,全部 31 項檢查 → 修正對應,錯誤 vs 警告 | references/plugin-check.md |
| 自動掃描硬性封鎖(隱藏檔案、資料庫、不允許的呼叫) | references/automated-scan.md |
| 4 個人為審查類別 + 回覆措辭 | references/review-categories.md |
| 真實審查教訓(範例→全面掃描,3 案例 nonce,phpcs:ignore≠跳脫,轉型≠淨化,路徑,fork 前綴) | references/real-review-lessons.md |
| 前綴命名所有全域項目(函數/選項/鉤子/控制代碼/CPT/中繼資料…) | references/prefixing.md |
淨化 · 驗證 · 跳脫 + $wpdb->prepare + webhook 簽章 | references/sanitization-escaping.md |
| 入隊 JS/CSS(依賴/版本、內嵌、條件控制、區塊、無原始標籤) | references/enqueue.md |
| i18n(文字網域==外掛別名、字串字面值、譯者註解、JS) | references/i18n.md |
| 權限與權限回呼(授權、REST、priv/nopriv) | references/capabilities.md |
| 政策規範(授權、遠端程式碼、追蹤、干擾、商標、揭露) | references/guidelines.md |
| readme.txt 格式規範(檔頭、標籤≤5、簡短說明≤150、螢幕截圖、外部服務) | references/readme-txt.md |
| Abilities API · AI Client · MCP(逐能力授權、資料邊界、破壞性確認、供應商揭露) | references/abilities-ai-mcp.md |
| 安全性補充(檔案/WP_Filesystem、上傳、SSRF、禁止/混淆呼叫) | references/security-extras.md |
| 卸載與生命週期(資料清理、不刪除使用者內容、多站點) | references/uninstall.md |
| 打包 zip + 版本/檔頭/readme 一致性 + .mo | references/packaging.md |
| 掃描/PHPCS 程式碼 → 修正查詢 | data/plugin-check-errors.csv |
快速上傳前檢查清單(複製貼上驗證)
AUTOMATED SCAN (硬性封鎖)
[ ] zip 中無隱藏檔案:`find . -name '.*' -not -name '.' -not -name '..'` 結果為空
(.gitkeep / .gitignore / .DS_Store / .editorconfig 都會造成 "hidden_files" 失敗)
[ ] zip 中無 node_modules/、.git/、tests/、scripts/、phpcs.xml、webpack.config.js
[ ] 所有外掛擁有的全域項目使用唯一且 >=4 字元的前綴(不可用 wp_ / __ / 單一 _ )
[ ] 無未經 prepare() 的直接 $wpdb 查詢;無遠端程式碼執行;無回傳資訊
MANUAL REVIEW (4 個類別)
[ ] 入隊:無原始 echo '<script>'/'<style>';使用 wp_enqueue_* + wp_add_inline_*
[ ] 每個改變狀態的 AJAX/admin_post 使用 nonce + current_user_can;webhook 使用
hash_equals 驗證供應商簽章後再使用(不可使用 WP nonce)
[ ] 每個 $_POST/$_GET 經過淨化(wp_unslash + sanitize_*);json_decode -> map_deep;
輸出在最後階段跳脫(esc_html/esc_attr/esc_url/wp_kses)
[ ] textdomain == 外掛別名;__() 的引數為字串字面值;/* translators: */ 放在 %s 之前
POLICY (人工審查,lint 工具抓不到 — references/guidelines.md)
[ ] 宣告 GPL 相容授權 + 包含 LICENSE 檔案;每個捆綁的函式庫/資產為 GPL 相容
[ ] 無遠端程式碼執行 / 不從 CDN 載入可執行的 JS;將資產捆綁於本地
[ ] 任何第三方 API 呼叫在 readme 的 "== External services ==" 中揭露(哪些資料、何時、URL)
[ ] 無未經明確選擇加入的資料收集/追蹤(預設關閉);無前端信用連結除非已選擇加入;
後台通知可關閉 + 僅限當前畫面(無干擾垃圾訊息)
[ ] 外掛別名/名稱不以你未擁有的商標開頭(woocommerce-… / google-…)
README.TXT (references/readme-txt.md — 在 wp.org readme 驗證器驗證)
[ ] 檔頭解析正確:Contributors=wp.org 使用者名稱,Tags<=5,Stable tag = 實際發佈版本
[ ] 簡短說明 <=150 字元,純文字;Changelog 中有 Stable tag 的條目
[ ] 螢幕截圖/banner/圖示放在 SVN /assets/ 中,**不**在 zip 中
AI / ABILITIES / MCP (如果外掛註冊了 abilities、呼叫 LLM 或暴露 MCP)
[ ] 每個 wp_register_ability 有真實的 permission_callback(物件層級權限),非 __return_true
[ ] 資料邊界:ability/MCP 工具只傳回當前使用者權限允許的資料
[ ] 模型/代理輸入視為不可信;破壞性 ability 需經確認才能執行
[ ] 捆綁的 LLM/AI SDK 為 GPL 授權;在 == External services == 中揭露供應商;金鑰永不記錄
[ ] MCP 端點需驗證(App Password/OAuth)且執行每個 ability 的 permission_callback
EXTRA SECURITY + LIFECYCLE
[ ] 無 eval/shell/obfuscation/extract;無殘留的 var_dump/print_r/error_log
[ ] 檔案僅透過 WP_Filesystem 寫入 uploads;上傳透過 wp_handle_upload + MIME 白名單
[ ] 遠端呼叫透過 wp_safe_remote_*(SSRF:封鎖內部範圍)+ 逾時 + 回應檢查
[ ] uninstall.php 由 WP_UNINSTALL_PLUGIN 保護;僅移除你前綴的資料;絕不刪除使用者內容
PACKAGE + METADATA
[ ] 版本同步:主檔案檔頭 Version、版本常數、readme Stable tag、Changelog
[ ] Requires 檔頭在主檔案和 readme.txt 兩者都一致(WP / PHP / WC)— 以及任何
在執行階段阻止啟用的 MIN_* 常數
[ ] vendor/ 由 `composer install --no-dev` 產生(無 squizlabs/phpunit/phpcompatibility)
[ ] zip 根資料夾名稱 == 外掛別名
工作流程
- 閱讀規則 針對該任務(參閱上述參考文件對應)— 從
references/automated-scan.md+references/review-categories.md開始,然後深入與發現問題相符的章節(prefixing.md/sanitization-escaping.md/enqueue.md/i18n.md)以及建置用的references/packaging.md。 - 建置乾淨的 zip — 執行
scripts/build-zip.sh(處理 git archive、移除開發檔案 + 所有點檔案、安裝vendor/使用--no-dev、以外掛別名作為根目錄壓縮)。絕不要手動壓縮工作目錄,否則開發垃圾和點檔案會滲入。 - 預檢 — 執行
scripts/preflight.sh <path-to-zip-or-dir>:掃描隱藏檔案、檢查版本同步 + 檔頭/readme 一致性、若已配置則執行 PHPCS。修正直到乾淨為止。 - 執行 Plugin Check (PCP) — 這是官方工具,可在本地執行與目錄相同的自動檢查。
wp plugin check <slug> --require=./wp-content/plugins/plugin-check/cli.php(或工具 → Plugin Check)。修正每個 ERROR,審查每個 WARNING。參閱references/plugin-check.md了解全部 31 項檢查、標誌以及每個技能章節對應的修正方式。 - 上傳。 如果自動掃描拒絕 → 讀取確切的錯誤碼,透過
data/plugin-check-errors.csv對應,修正、重建、重新上傳。 - 人工審查回覆 — 當審查員 email 告知問題時,全面修正每個問題(範例只是抽樣 — 在整個樹中 grep、對整個外掛執行 Plugin Check + PHPCS;同一類別重複出現 = 立即再次拒絕)。然後逐點回覆,說明做了哪些修改。參閱
references/review-categories.md了解標準修正 + 回覆措辭,以及references/real-review-lessons.md了解每個類別的全面掃描指令和那些首次修正仍會遺漏的陷阱。
血淚教訓(最昂貴的那些)
.gitkeep會使自動掃描失敗。 它是隱藏檔案。從套件中移除每個點檔案;從 git 中刪除.gitkeep以免git archive重新加入它。- Requires 版本漂移。 wp.org 同時讀取主檔案檔頭和 readme.txt;執行階段的
MIN_WC/MIN_PHP常數可以獨立阻擋啟用。三者必須一致,否則低版本使用者安裝成功後會遇到功能異常。 - Webhook 不能使用 WP nonce。 審查員會標記 IPN handler "沒有 nonce" — 這是誤判;正確做法是使用
hash_equals()進行供應商簽章驗證。用逐行phpcs:ignore+ 理由來註明。 json_decode不是淨化。 務必對結果使用map_deep(sanitize_text_field)。composer install(未加--no-dev) 會將 phpcs/phpunit 洩漏到 vendor/ 中 → 體積膨脹、被標記。出貨的 zip 務必使用--no-dev。