【ClaudeCode】クラウド版Claude CodeからWordPressのREST APIが403になった原因と対処|?rest_route=で通った

スポンサーリンク
ClaudeCode
スポンサーリンク

うちのブログは、記事の下書き作成から WordPress への投稿までを Claude Code に任せている(ブログ記事の下書き作成からWordPress投稿までを自動化してみた)。これまでは自宅の PC で動かしていたが、ブログ作業をクラウド版の Claude Code(Claude Code on the web)に移そうとしたところ、WordPress の REST API がすべて 403 で弾かれた。

結論から書くと、認証情報の問題ではなかった。サーバー側で /wp-json/wp/v2/ というパスへのアクセスが止められていて、?rest_route= 形式の URL にすると同じ API に届いた。切り分けの手順と結果をまとめておく。

起きたこと

クラウドのセッションに、自宅 PC と同じ WordPress のユーザー名とアプリケーションパスワードを環境変数で渡した。そのうえで、まずは読み取りだけで接続を確かめようとしたが、認証なしでも認証ありでも、返ってくるのは WordPress の JSON のエラーではなく、Apache の素っ気ない 403 のページだった。

<title>403 Forbidden</title>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>

自宅 PC からは同じ認証情報で問題なく投稿できているので、パスワードが間違っているとは考えにくい。WordPress まで届く前に、サーバーか WAF で止められていると見当をつけた。

切り分けの手順

切り分けは Claude Code に任せた。投稿・更新・削除はせず、GET の読み取りだけに限定するよう指示している。Claude Code が実際に使ったのは次のような curl である(WP_URL・WP_USER・WP_APP_PASSWORD は環境変数)。

curl -sS -o /dev/null -w "%{http_code}\n" -u "$WP_USER:$WP_APP_PASSWORD" "$WP_URL/wp-json/wp/v2/users/me?context=edit"

URL と認証の有無を1つずつ変えて試した結果が次の表である。

リクエスト 結果
トップページ(/) 200
/wp-json/(認証あり・なし) 200
/wp-json/wp/v2/posts(認証なし) 403
/wp-json/wp/v2/posts(認証あり) 403
/wp-json/wp/v2/posts(でたらめな認証) 403
/wp-json/wp/v2/users/me(認証あり) 403
/?rest_route=/wp/v2/users/me(認証あり) 200
/?rest_route=/wp/v2/users/me(でたらめな認証) 401

ポイントは3つある。

  • /wp-json/wp/v2/ 以下は、認証があってもなくても、でたらめな認証でも同じ 403。認証の中身を見る前に止められている
  • /wp-json/ そのものは 200。REST API が丸ごと無効になっているわけではなく、/wp/v2/ 以下のパスだけが止められている
  • ?rest_route= 形式なら、正しい認証で 200、でたらめな認証で 401。401 は WordPress 自身が返すエラー(rest_not_logged_in)なので、リクエストが WordPress まで届いて、認証がきちんと判定されている

?rest_route= とは

WordPress の REST API には、同じ API を呼ぶ URL の書き方が2つある。

  • https://(サイト)/wp-json/wp/v2/posts
  • https://(サイト)/?rest_route=/wp/v2/posts

普段使っている /wp-json/ の形は、パーマリンク設定(きれいな URL)が有効なときの書き方で、?rest_route= はパーマリンクに頼らない書き方である。中身は同じ API なので、パスに対する制限には引っかからない。ほかのパラメータは ? ではなく & でつなぐ。

curl -sS -u "$WP_USER:$WP_APP_PASSWORD" "$WP_URL/?rest_route=/wp/v2/posts&per_page=5&context=edit"

これで、管理者として下書きも含めた投稿の一覧が取れた。

なぜクラウドからだけ止められたのか

自宅 PC からは /wp-json/wp/v2/ で問題なく投稿できているので、違いは接続元である。クラウドのセッションは国外のデータセンターから接続している。

うちのブログは ConoHa WING で動いていて、ConoHa WING には「海外アクセス制限」という機能がある。場所は、コントロールパネルの「WING」→「サイト管理」→「サイトセキュリティ」→「WordPressセキュリティ」タブの「海外アクセス制限」で、「ダッシュボード」「XML-RPC-API」「REST-API」「wlwmanifest.xml」「Abilities API」の項目ごとに ON/OFF を切り替えられる。初期状態はすべて ON である(ConoHa WING サポート「WordPressのセキュリティの設定をする」)。

管理画面で確認したところ、うちのサイトはこの5項目がすべて ON になっていた。クラウドからは REST API のほかに wp-login.php・wp-admin・xmlrpc.php も同じ 403 になっていて、ダッシュボードと XML-RPC-API の制限も効いている。/wp-json/wp/v2/ が止められたのは、この「海外アクセス制限」の「REST-API」によるものである。

?rest_route= で抜けられることの注意点

裏を返すと、海外アクセス制限の「REST-API」は ?rest_route= 形式には効いていない。公開記事が読めるのはもともと誰でも見られる情報なので問題ないが、確かめてみると、ユーザー一覧(/?rest_route=/wp/v2/users)も国外から認証なしで見えた。ユーザー一覧にはログインに使う名前が含まれる。

ログイン画面は国外から 403 で、REST API の認証はランダムなアプリケーションパスワードなので、すぐに危ないわけではない。それでも見せる必要のない情報なので、ログインしていない人にはユーザー一覧を返さないようにした。Code Snippets プラグインに次のコードを登録している。

add_filter( 'rest_endpoints', function ( $endpoints ) {
	if ( ! is_user_logged_in() ) {
		unset( $endpoints['/wp/v2/users'] );
		unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
	}
	return $endpoints;
} );

登録後は、認証なしの /?rest_route=/wp/v2/users が 404(rest_no_route)になった。アプリケーションパスワードで認証したリクエストはログイン中として扱われるので、自分の投稿スクリプトからの users/me などは今までどおり 200 で使える。

ログイン名は /?author=1 のような URL からも分かる(/author/(名前)/ にリダイレクトされる)ので、こちらは SiteGuard WP Plugin の「ユーザー名漏洩防御」を ON にして塞いだ。ON にした後は、トップページにリダイレクトされるようになった。

また、?rest_route= 形式は書き込みにも使える。実際に、クラウドから既存記事の更新(form-urlencoded の POST)が通ることを確かめた。

この記事のまとめ

この記事のまとめ
  • クラウドから WordPress の REST API が 403 になっても、すぐに認証情報を疑わない。でたらめな認証でも同じ 403 なら、WordPress の手前で止められている
  • /wp-json/ が 200 で /wp-json/wp/v2/ 以下だけ 403 なら、パスに対する制限
  • ?rest_route=/wp/v2/... 形式にすると同じ API に届く(書き込みも通る)
  • 海外アクセス制限は ?rest_route= には効かないので、ユーザー一覧は別に塞いでおく

コメント

タイトルとURLをコピーしました