知識庫
AI工作流
11 min

WP-CLI × Claude Code 實測:七個坑、一個 SQL injection,和一組能救你的護欄

WP-CLI 搭 Claude Code 自動化 WordPress,實際做起來會撞到什麼?我從零架了一個站來實測:環境三連坑、SQLite 讓 wp db export 靜默失敗、官方 Plugin Check 跑出 20 項違規含真的 SQL injection。附可直接複製的護欄三件套,以及我自己寫錯的那個 fail-open 漏洞。

我沒有 WordPress 網站。這篇的第一件事,就是為了寫它而從零架了一個。

因為這個題目最爛的寫法,就是把官方文件的指令抄一遍,配上「三步驟輕鬆自動化」的標題。真正有用的東西不在文件裡,在你安裝到一半炸掉的那些地方。所以我裝了 PHP、裝了 WP-CLI、架了 WordPress 7.0.2、寫了外掛、寫了護欄,然後把每個絆倒我的地方記下來。

七個坑,一個真的 SQL injection,還有一個我自己寫的、差點放行 DELETE FROM wp_posts 的資安漏洞。以下全部有實測輸出。

WP-CLI 裝起來就三個坑:環境根本沒那麼好裝

我用 brew install php wp-cli。第一次直接失敗,而且失敗得很陰險:

brew install php wp-cli 2>&1 | tail -25

回報成功,實際上整包沒裝(brew 鎖衝突)。原因是 pipe 後面接了 tail,exit code 被 tail 吃掉了。這個坑對 AI agent 特別致命,因為它會以為裝好了,繼續往下做。修法是 set -o pipefail

裝好之後,wp 一跑就吐:

PHP Deprecated: Case statements followed by a semicolon (;) are deprecated
in .../wp-cli/2.12.0/.../react/promise/src/functions.php

這不是我的錯,是 WP-CLI 穩定版停在 2.12.0 已經 14 個月,而 PHP 已經走到 8.5。順帶一提,新的 wp aiwp ability 指令目前只存在 nightly 的 2.13.0-alpha,brew 裝不到。

接著 wp core download

cURL error 77: error adding trust anchors from file:
/usr/local/etc/openssl@3/cert.pem

ca-certificates 明明裝了,但 openssl@3 少了那條 symlink。補上就好:

ln -sfn /usr/local/etc/ca-certificates/cert.pem /usr/local/etc/openssl@3/cert.pem

HTTPS 通了,下一個:

PHP Fatal error: Allowed memory size of 134217728 bytes exhausted

PHP 預設 128MB 不夠解壓 WordPress 核心。最後我把三件事包成一個 wrapper 才穩定:

php -d error_reporting="E_ALL & ~E_DEPRECATED" -d memory_limit=512M /usr/local/bin/wp "$@"

這四段全是文件不會寫的東西,但你不解決就一步都走不下去。

先有雞還是先有蛋:WP-CLI 裝 SQLite 的安裝悖論

我不想在機器上留一個常駐的 MySQL,所以走 SQLite。結果:

wp plugin install sqlite-database-integration
→ Error: 建立資料庫連線時發生錯誤

wp plugin install 執行前要先連資料庫,而我要裝的正是提供資料庫的那個外掛。

繞法是直接抓 zip,然後這步官方文件講得很含糊:複製 db.copywp-content/db.php 之後,裡面有兩個佔位符要手動替換。

sed -i '' "s#{SQLITE_IMPLEMENTATION_FOLDER_PATH}#$(pwd)/wp-content/plugins/sqlite-database-integration#g" wp-content/db.php
sed -i '' "s#{SQLITE_PLUGIN}#sqlite-database-integration/load.php#g" wp-content/db.php

不換這兩個,wp core install 就一直失敗。

最毒的雷:SQLite 站上 wp db export 是啞的

社群公認讓 agent 碰資料庫的安全儀式,是「動手前先 wp db export 備份」。我在 SQLite 站上實測了這道保險:

exit code127
備份檔是否產生否,檔案根本沒出現
輸出含 Error 字樣否 ← 致命
輸出含 Success 字樣

完整輸出只有這樣:

env: mysqldump: No such file or directory
Warning: Failed to get current character set of the posts table.

因為 wp db export 是 shell out 去呼叫 mysqldump,而 SQLite 站上根本沒有 MySQL。

它失敗了,但訊息裡沒有 Error 字樣。任何用 grep -i error 判斷成敗的自動化腳本,都會認為備份成功。再把它跟第一個坑疊起來(pipe 吃掉 exit code),你就得到一個完美的災難配方:腳本回報備份成功、實際上沒有備份、然後放心去跑破壞性指令。

能用的替代方案是 wp export(WXR 格式),我實測可用。wp postwp option 這些結構化指令也都正常,壞掉的只有需要原生 MySQL 的那些。可以直接抄的防禦是:備份後不要信任訊息文字,去驗檔案本身。

wp export --dir=./backups
[ -s ./backups/*.xml ] || { echo "備份失敗,中止"; exit 1; }

20 項變 0 項:有沒有家規,差在這裡

前面都是環境問題。這段講程式碼。

我寫了一個約 50 行的公告外掛(後台可編輯、前台顯示、記錄瀏覽次數),刻意用「一句話 prompt 會產出的那種寫法」。然後用官方的 Plugin Check 跑分,不自己評:

wp plugin install plugin-check --activate
wp plugin check moon-notice

結果 7 個 ERROR、13 個 WARNING,總共 20 項。而且完全命中社群公認的四大盲點:

NonceVerification.Missing× 3
ValidatedSanitizedInput.InputNotSanitized× 3
EscapeOutput.OutputNotEscaped× 3
PreparedSQL.InterpolatedNotPrepared× 1

最後那個不是理論問題,是真的可以被利用的 SQL injection:

$page = $_SERVER['REQUEST_URI'];
$wpdb->query("INSERT INTO {$wpdb->prefix}moon_views (page) VALUES ('$page')");

網址列就是輸入點。另外還有兩個不寫就一定中的低級錯:缺 License 標頭、缺 if ( ! defined( 'ABSPATH' ) ) exit;

然後我依照 WordPress 安全規範重寫同一個外掛,再跑一次:

Plugin Check 對照結果

裸奔版:7 ERROR + 13 WARNING = 20 項

有家規版:2 ERROR + 0 WARNING = 2 項

剩下那 2 項是 readme_*_non_official_language,只因為我 readme 寫中文,跟程式安全無關。安全問題從 20 項降到 0 項。

差別不在模型聰不聰明,在於有沒有人告訴它 WordPress 的規矩。所以把規矩寫下來的那 20 分鐘投資,第一次 session 就回本。

護欄三件套,以及我自己寫錯的那一版

標準架構是三層:wp-cli.yml 管連線、CLAUDE.md 寫家規、.claude/settings.json 設 allow/deny。但 deny 清單有個限制:遇到 --dangerously-skip-permissions 就破功。唯一硬擋得住的是 PreToolUse hook(其他好用的Claude Code 指令與設定我另外寫過)。

所以我寫了一支 wp-guard.sh,擋這些:資料庫刪除重置、原生 SQL、沒加 --dry-run 的 search-replace、指向 @prod、讀取 wp-config.php、一次更新全部外掛。寫完測試,結果:

🛑 擋下 | wp db drop --yes
✅ 放行 | wp db query "DELETE FROM wp_posts"   ← 這行不該過

追下去發現,我的 hook 是這樣取指令的:

cmd="$(printf '%s' "$payload" | jq -r '.tool_input.command // empty')"
[ -z "$cmd" ] && exit 0     # ← 問題在這

jq 解析失敗時 cmd 是空的,然後 exit 0 直接放行。

fail-open 的護欄,比沒有護欄更危險,因為你以為自己被保護著。

修成 fail-closed(解析失敗改掃原始 payload、payload 空就直接擋)之後重測,7 個危險指令全擋、3 個唯讀指令全放行,連「畸形 JSON 裡藏著 wp db query」都攔得下來。這種東西照文件抄一輩子都遇不到,只有真的動手寫、真的去測才會撞見。

我的想法 — 九黎月怎麼看這件事

做完這輪我一直想到一個詞:入門。

戲班收徒不是先教唱,是先掃地、先站樁、先學規矩。外行人覺得那是折騰,學了兩年還沒上台。可是規矩不是拿來限制你的,是替你擋掉那些你還看不見的坑。等你真的站上台,那些沒學規矩的人正在台上手忙腳亂。

AI 寫 WordPress 現在就是這樣。它能在兩小時裡生出一個能跑的外掛,那是它的天分,快得嚇人。但 20 項違規裡有一個真的 SQL injection,而它自己不知道。Patchstack 的 2026 報告說 2025 年 WordPress 生態新增 11,334 個漏洞,年增四成二,九成一出在外掛,而且點名了 vibe coding。有人 48 小時做出多語外掛,上線當天炸三個 bug;有人兩三天寫完,花了七週才過官方審查。

快的是「能跑」,不是「能上線」。這兩件事之間的距離,就是規矩。

所以護欄不是綁住 AI 的繩子,是給它的戲班規矩。你把家規寫進 CLAUDE.md、把紅線寫進 hook,它就從一個很有天分但沒師父的新人,變成一個知道哪裡不能踩的角兒。

順帶一提,那個 fail-open 的漏洞是我自己寫的。連寫護欄的人都會寫錯護欄,所以護欄本身也要測。

結論:讓 WP-CLI 交給 Claude Code 之前,先做這六件事

1. 環境包成 wrapperdeprecation、記憶體、憑證三件事一次解決,不要每次重踩
2. 絕不碰正式站用 Playground 或本機複本,@prod 直接寫進 hook 黑名單
3. 備份要驗檔案不要信訊息文字,wp db export 在 SQLite 上是啞的
4. 寫 CLAUDE.mdnonce、sanitize、escape、$wpdb->prepare() 列成硬規定
5. 用 Plugin Check 當量尺程式碼好不好有客觀分數,不必用感覺爭論
6. hook 要 fail-closed而且要測它,護欄本身也會有洞

做完這六件事,AI 寫 WordPress 就從賭博變成工程。

全文數據為 2026-07-28 本機實測:macOS、PHP 8.5.8、WP-CLI 2.12.0、WordPress 7.0.2 + SQLite、Node v22.18.0。外部數據來源:WP-CLI 官方 handbook、WordPress Playground 官方文件、Patchstack《State of WordPress Security in 2026》。

標籤
#WP-CLI#Claude Code#WordPress 自動化#WP-CLI 教學#AI 寫程式#WordPress 外掛開發#九黎月