0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PR: フリーランスコネクト株式会社
フリーランスコネクト採用ページ

【2026年10月】Laravel/PHP 今月のまとめ|13.35→13.39、PHP 本体の緊急修正、レプリカの古い読み取りを防ぐ

0
Posted at

2026年10月の Laravel News を1本にまとめた。コアは 13.35 から 13.39 の4リリース(13.37 は Laravel News に記事なし)。今月はセキュリティの告知が目立った。PHP 本体の緊急リリース、Laravel の token ガードの認証バイパス、AI SDK と MCP の修正、NativePHP for Mobile の告知7件が重なった。対象は34本。10月公開の39本から5本(動画連載の紹介2本、翌日の正式記事に内容が引き継がれた予告1本、有料講座の告知1本、個人プロダクトの謝辞寄稿1本)を除いた。

上から順に読む記事ではなく、表で流し見して、気になったものだけ本文か出典リンクに飛ぶ構成にした。主要なセクションの頭には一覧表を置いたので、そこだけ見て終わりでも構わない。

出典はすべて Laravel News の各記事。バージョン番号やメソッド名は原文に合わせている。

TL;DR

  • コアは4リリース。読み取りレプリカを使うアプリ向けの機能が続いた。書き込み直後の読み取りを書き込み側へ送る PreventStaleDatabaseReads ミドルウェアと DB::usingWritePdo()(13.38)、キューワーカーのジョブ間で sticky 状態を捨てる Worker::$flushStickyConnections(13.36)、PlanetScale の行数制限を外して集計する DB::olap()(13.36)。
  • バリデーションは文字列ルールの全部に Rule:: のビルダーが付いた(13.36)。カンマを含む値が2つに割れなくなる。Rule::array() には min() や max() も入った(13.39)。
  • スケジューラとセッションの運用系。全タスクを1台で動かす Schedule::alwaysOnOneServer() と、長いコマンドがデプロイの中断を見る hasBeenInterruptedSince()(13.35)、期限切れセッションを掃除する session:garbage-collect(13.39)。
  • 上げる前に見ておきたい挙動の変更は3か所。Mercure へのブロードキャストが既定で同期になり(13.36)、子モデルの #[Connection] が親の $connection より優先され(13.38)、テストで Mail::html() がフェイクされ assertSeeText('0') が素通りしなくなる(13.39)。
  • セキュリティ。PHP 本体に未認証で RCE に至る critical が2件(CVSS 9.2)、Laravel の token ガードに認証バイパス(9.2、8月25日の 12.68.0 と 13.29.0 で修正済み)、AI SDK 1.0.1 と MCP 1.0.1、NativePHP for Mobile 4.6.1。
  • 周辺では Laravel AI SDK 1.1 が Agent Skills に対応。Inertia は 3.8 と 3.9 で SSR 向けの WhenMounted とバックグラウンドのタブでのポーリング停止。Filament 5.8 はスキーマの遅延読み込み。Laravel News の Eric Barnes が Matt Stauffer と、PHP 全般を扱う PHP News を開設した。

コア早見表

バージョン 目玉 上げるときの注意
13.35 Route::query() / モデルの defaults()(トレイトで opt-in) / フェイクの配列アサーション / queue:work --memory=60% / Schedule::alwaysOnOneServer() と hasBeenInterruptedSince() 出典に挙動変更の明記なし。--memory に割合を渡すときは、memory_limit が -1 だとワーカーが止まらない
13.36 全ルールの Rule:: ビルダー / DB::olap() と isVitess() / Worker::$flushStickyConnections / キュー投入する Mailable の #[DeleteWhenMissingModels] と #[FailOnTimeout] Mercure のブロードキャストが既定で同期になった。キューに戻すなら MERCURE_ASYNC=true
13.38 PreventStaleDatabaseReads ミドルウェア / DB::usingWritePdo() / リスナーと Mailable と通知にも #[CountCrashesAsExceptions] / lazy() のメモリ半減 親クラスが $connection を持つとき、子の #[Connection] が無視されていた不具合の修正。子のクエリの行き先が変わる
13.39 session:garbage-collect / #[Table(key: false)] / JobAttempted の duration / Rule::array()->min() 系 / #[WithoutMigrationEvents] / config:cache などのキャッシュファイルをアトミックに書く MailFake が Mail::html() と Mail::plain() も捕まえる。assertSeeText('0') が素通りしなくなる。RefreshDatabase が開いたままのトランザクションを全段ロールバックする

13.37 は Laravel News にリリース記事が無いので、ここでは扱っていない(13.38 の記事に「13.37 で dispatch() とキュー投入リスナーの unique ロック解放を修正した」とある)。

Laravel コアの進化(13.35 → 13.39)

13.35:Route::query() とモデルの defaults()、スケジューラの複数サーバー対応

Route::query() は HTTP の QUERY メソッドを受けるルートを登録する。QUERY はリクエストボディを持てる安全なメソッドで、クエリ文字列に収まらないフィルタを持つ検索エンドポイントに向く。13.19 で Http::query() とテスト用の query() が入っていたが、ルート定義は Route::match(['QUERY'], ...) と書く必要があった。QUERY がルーターの動詞一覧に入ったので、Route::any() と Route::redirect() も QUERY リクエストに一致する。

Route::query('/products/search', SearchProductsController::class);

Laravel 14 向けにマージされた版と違い、Registrar コントラクトは変えず、CSRF 保護は POST と同じように QUERY を検証する。web ミドルウェアグループの QUERY ルートにはトークンか except の登録が要る。

モデルの defaults() は、$attributes プロパティのメソッド版。PHP のプロパティ初期値は定数式に限られるので、config() や now()、フィーチャーフラグで決まる既定値は書けなかった。Laravel 14 の機能として先に記事が出て、13.35 に opt-in のトレイトとして入った。13 では HasDefaultAttributes を使ったモデルでだけ動く。

class Subscription extends Model
{
    use HasDefaultAttributes;

    protected function defaults(): array
    {
        return ['cancelled' => false, 'trial_days' => Feature::active('extended-trials') ? 30 : 14];
    }
}

トレイト経由にしたのは、すでに defaults() という名前のメソッドを持つモデルを巻き込まないため。defaults() の値は $attributes より優先され、キャストもミューテタも通らない生の値として扱われる。

フェイクのアサーションが、コールバックの代わりにプロパティの配列を受け取るようになった。Queue、Bus、Event、Notification の4つが対象。

Queue::assertPushed(ProcessPodcast::class, [
    'podcast' => $podcast,
    'user' => $user,
    'status' => 'pending',
]);

比較は厳密等価で、Eloquent モデル同士だけ is() で比べる。フェイクが外から読むので対象は public プロパティに限られる。

queue:work --memory が PHP の memory_limit に対する割合を受け取る。--memory=60% なら、memory_limit が 512M のとき約 307 MB でジョブの区切りで止まる。コンテナのサイズに合わせてワーカーの上限が追従する。memory_limit が -1(CLI ではよくある)だと割合が負の値になり、メモリではワーカーが止まらない。割合を使うならワーカーのプロセスに memory_limit を設定する。

スケジューラの複数サーバー対応は2つ。Schedule::alwaysOnOneServer() は全タスクに onOneServer() を掛ける。これまでは routes/console.php 全体を Schedule::onOneServer()->group() で包んでいた。名前の無いクロージャタスクはロックのキーが作れないので対象外になり、全サーバーで動き続ける。

// AppServiceProvider::boot()
Schedule::alwaysOnOneServer();

Schedule::hasBeenInterruptedSince() は、runInBackground() で動く長いコマンドが schedule:interrupt を見るための手段。これまで schedule:interrupt は、schedule:run が秒単位のタスクのために回す1分未満のループしか止めず、裏で走るコマンドには届かなかった。queue:restart と同じようにタイムスタンプを保存する方式に変わり、コマンドは開始時刻と比べてバッチの区切りで止まれる。

ほかに入ったもの。

  • response()->markdown():text/markdown のレスポンスを返す。リクエスト側の wantsMarkdown() と対になる。
  • lazy_root_creation:local ディスクのルートディレクトリを、ディスク構築時ではなく最初の書き込み時に作るオプション。
  • 修正(リリースノートより):キャストした主キーのモデルで chunkById() と lazyById() が無限ループする問題、未知フィールドで失敗させる設定が _token と _method を未知フィールドと数える問題。ほかはリリースノートへ。

出典:https://laravel-news.com/laravel-13-35-0

13.36:全バリデーションルールの Rule:: ビルダーと DB::olap()

文字列でしか書けなかったバリデーションルールの全部に Rule:: のメソッドが付いた。required_without、accepted_if、url、uuid、distinct などが IDE の補完で書ける。

$request->validate([
    'phone' => [Rule::requiredWithout('email')],
    'website' => ['nullable', Rule::url()->protocols(['https'])],
    'device_id' => [Rule::uuid()->version(4)],
    'tags.*' => [Rule::distinct()->ignoreCase()],
]);

Rule::required() のようにパラメータの無いルールは、そのまま文字列を返す。フィールド名や値を取るルールは、パラメータを配列のままバリデータに渡すオブジェクトを返す。文字列形式ではカンマで分割されていたので、accepted_if:plan,Team, Annual のような値は2つに割れて一致しなかった。ビルダーならカンマを保てる。同じ PR で Rule::date() に equals() も入った。

DB::olap() は、PlanetScale などの Vitess 系データベースで、コールバックの間だけワークロードを OLAP に切り替える。PlanetScale の既定の OLTP では1クエリの結果が 100,000 行までで、集計やエクスポートがその上限で落ちる。読み取りと書き込みの両接続にワークロードを設定し、finally で OLTP に戻す。Vitess 以外ではコールバックを呼ぶだけなので、手元の MySQL でも同じコードが動く。同じ PR で MySQL 接続に isVitess() も入った。

$revenue = DB::olap(fn () => Order::query()
    ->selectRaw('customer_id, sum(total) as revenue')
    ->where('created_at', '>=', now()->subYears(3))
    ->groupBy('customer_id')
    ->get()
);

Worker::$flushStickyConnections は、キューワーカーがジョブを取る前に sticky 状態を捨てる opt-in のフラグ。sticky を有効にした読み書き分離の接続では、書き込みのあとの読み取りが書き込みサーバーへ行く。Web リクエストならレスポンスまでで終わるが、ワーカーは1プロセスで何千ジョブも処理するので、1つのジョブが書くと、以降の全ジョブの読み取りが書き込みサーバーに向いたままになっていた。

// AppServiceProvider::boot()
Worker::$flushStickyConnections = true;

既定で無効なのは、同じワーカーの前のジョブが書いたデータを読む前提のジョブがあり得るため。

キュー投入する Mailable が #[DeleteWhenMissingModels] と #[FailOnTimeout](と対応するプロパティ)を無視していた問題が直った。注文が削除されたあとに送られるメールのジョブは、ModelNotFoundException で失敗せずに削除される。Markdown メールでは、リンクのテキストに入れたユーザー入力に、バックスラッシュで閉じ括弧をエスケープしてリンク先を差し替える細工ができる問題が修正された。

上げるときの注意:Mercure へのブロードキャストが、キューに入れずにその場で送られるようになった。Mercure 接続だけを対象にするイベントは ShouldBroadcastNow と同じ扱いになり、ほかの接続も対象にするイベントはこれまでどおりキューに入る。以前の挙動に戻すなら MERCURE_ASYNC=true を設定する。非同期にするとワーカーが HTTP で発行するので Mercure の url が必須になる。

出典:https://laravel-news.com/laravel-13-36-0

13.38:PreventStaleDatabaseReads と usingWritePdo()

PreventStaleDatabaseReads は、読み取りレプリカを使うアプリ向けの opt-in ミドルウェア。リクエストが既定の接続に書き込むと、セッションにタイムスタンプを保存する。そのユーザーの次の2秒間の読み取りは書き込みサーバーへ行き、それを過ぎるとレプリカに戻る。sticky は同じリクエストの中でしか効かないので、PUT のあとに GET へリダイレクトするフォームでは、レプリカの遅延で古いデータが見えることがあった。

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        PreventStaleDatabaseReads::class,
    ]);
})

追跡する秒数は PreventStaleDatabaseReads::for(10) のように指定できる。セッションが要り、既定の接続だけを追跡する。書いた本人の読み取りだけが対象なので、ほかのユーザーはレプリカの古いデータを読むことがある。

DB::usingWritePdo() は、コールバックの中の読み取りを全部書き込み接続へ送る。既存の onWriteConnection() は起点のクエリにしか効かず、結果から読んだリレーションはレプリカを読んでいた。コールバックが例外を投げても設定は戻る。

$posts = DB::usingWritePdo(fn () => User::query()
    ->where('email', $email)
    ->first()
    ?->posts()
    ->whereNull('published_at')
    ->get()
);

13.34 の #[CountCrashesAsExceptions] が、キュー投入するリスナー、Mailable、通知でも動くようになった。これまではジョブだけが対象で、$tries が 0 のリスナーはワーカーがメモリ不足で落ちても maxExceptions に数えられず、無制限に再試行されていた。

lazy() と lazyById() が、次のチャンクを取る前に前のチャンクを解放する。これまでは2チャンクが同時にメモリに載っていたので、ピークが必要量の約2倍だった。chunk() と chunkById() はもともとこの挙動。

ほかに入ったもの。

  • unique ジョブ:Schedule::job() で投入する unique ジョブが、キューへの push に失敗してもロックを握ったままになる問題の修正。uniqueFor が既定の 0 だとロックが切れず、キューが戻っても以降の実行が全部スキップされていた。ShouldBeUnique なブロードキャストイベントもスキップ時に UniqueJobSkipped を発火する。
  • 修正(リリースノートより):マシンをまたいでキャッシュしたルートでヘルスルートが動かない問題、日付以外の値で日付比較ルールが TypeError を投げる問題。ほかはリリースノートへ。

上げるときの注意:親クラスが $connection プロパティを持つとき、子モデルの #[Connection] 属性が無視されていた。子のクエリはエラーも出さずに親の接続へ流れていた。修正後は子の属性が勝つので、#[Connection('reporting')] を付けた子モデルのクエリは reporting 接続へ向く。両方を書いているモデルがあれば、どちらの接続で動いていたかを確認してから上げたい。

出典:https://laravel-news.com/laravel-13-38-0

13.39:session:garbage-collect と主キーの無いモデル

session:garbage-collect は、セッションドライバのガベージコレクションを走らせる Artisan コマンド。Laravel は config/session.php の lottery に従って、ランダムなリクエストの中で期限切れセッションを消していた。コマンドをスケジュールに載せ、lottery を [0, 100] にすれば、Web リクエストは掃除を待たなくなる。

Schedule::command('session:garbage-collect')->hourly();

#[Table(name: 'activity_log', key: false)] でモデルの主キーを無効にできる。これまで key に null を渡すと $primaryKey プロパティ(既定は id)にフォールバックしていたので、プロパティも書く必要があった。後続の PR でこのモデルは非インクリメントとして扱われ、create() が insert 後に例外を投げなくなった。

JobAttempted イベントに duration が付いた。これまで JobProcessed にしか無かったので、失敗したジョブや再試行のためにリリースされたジョブの所要時間は取れなかった。単位はミリ秒。dispatchSync() で動く sync キューにも同じ値が入る。

Event::listen(function (JobAttempted $event) {
    if (! $event->successful()) {
        Log::warning('Job failed', ['duration_ms' => $event->duration]);
    }
});

Rule::array() に min()、max()、between()、exactly() が付き、Rule::string() と揃った。[Rule::required(), Rule::array()->min(1)->max(10)] のように書け、Rule::array(['a', 'b'])->min(1) は array:a,b|min:1 になる。

テストまわりの修正がまとまって入った。

  • #[WithoutMigrationEvents]:LazilyRefreshDatabase がテスト中に流すマイグレーションのクエリを、DB::listen() と expectsDatabaseQueryCount() から外す。
  • BusFake::withoutDispatchingAfterResponses():ほかのジョブをフェイクしたまま、after-response のジョブを実行させる。
  • プロセスフェイク:終了コードと例外の短縮形が start() と pool() でも使える。

リレーションの作成系も直った。BelongsToMany に forceCreate() と forceCreateQuietly() が付いた。これまではクエリビルダーに流れてモデルは作られるが attach されなかった。restoreOrCreate() と createOrRestore() もリレーションを通るようになり、has-many で外部キーが入らない、belongs-to-many で別のモデルを復元する問題が直った。HasManyThrough と HasOneThrough も対象。

config:cache、route:cache、event:cache が一時ファイルに書いてからリネームする。コマンドの途中で起動したリクエストが空のキャッシュファイルを読み、opcache.validate_timestamps=0 だと PHP-FPM を再起動するまで全リクエストが落ちることがあった。

上げるときの注意:テストの挙動が3つ変わる。Mail::html() と Mail::plain() が MailFake で捕まえられる(これまではテスト中に実メールが送られていた)。assertSeeText('0') がレスポンスを確認せずに通っていた問題が直った。RefreshDatabase が各テスト後に開いているトランザクションを全段ロールバックするので、1つのテストが開いたままにしたトランザクションが後続を落とさなくなる。通っていたテストが落ち始めたら、この3つを疑う。

出典:https://laravel-news.com/laravel-13-39-0

機能別の解説記事に飛ぶ

コアの機能のうち8つには、Laravel News 側に単体の解説記事がある。実装するときはこちらが早い。

対象 解説記事 内容
13.35 モデルの defaults() $attributes と引数のどちらが勝つか、生の値(キャストを通らない)、取得した行ごとに走る点、既存の defaults() メソッドと衝突しないかの確認
13.35 フェイクの配列アサーション 厳密等価と is()、Carbon などの値オブジェクトはコールバックが要る、ネストしたキーは指定できない
13.35 複数サーバーのスケジューラ 名前の無いクロージャは対象外、Schedule::useCache() で共有ストアを指定、hasBeenInterruptedSince() を使うコマンドの実装例とデプロイスクリプト
13.36 Rule ビルダー パラメータ無し、フィールドや値を取る、オプション付きの3分類の全一覧、ParameterizedRule とカンマの扱い
13.36 DB::olap() 手書きの set workload との違い、cursor() や lazy() はコールバックの中で回す、isVitess()、PlanetScale が推奨する用途
13.36 キューの sticky リセット sticky の仕組み、database キュードライバが jobs テーブルの update で sticky を立て直す罠と DB_QUEUE_CONNECTION での回避
13.38 PreventStaleDatabaseReads 3ステップの仕組み、for() の秒数はレプリカ遅延の実測から決める、readWriteType でどちらを読んだかをテストする方法(RefreshDatabase では検証できない)
13.39 session:garbage-collect ドライバ別の要否、lottery の止め方、複数サーバーでの onOneServer()、13.38 以前で同じことをするクロージャ

10月のセキュリティ告知

PHP 本体、Laravel 本体、公式パッケージ、NativePHP と、層の違う告知が同じ月に並んだ。影響の有無を確かめる順に表にした。

対象 深刻度 影響する条件 修正版
PHP 本体の RCE 2件 Critical(CVSS 4.0 で 9.2) Web リクエストを受ける全構成(PHP-FPM、mod_php、php-cgi、FrankenPHP、LiteSpeed) 8.5.12 / 8.4.27 / 8.3.36 / 8.2.35
Laravel の token ガード認証バイパス Critical(9.2) config/auth.php に driver => 'token' のガードがあり、MySQL か MariaDB を使っている 12.68.0 / 13.29.0(8月25日公開)
同記事:JSON:API リソースのデータ破壊 Medium(CVSS 3.1 で 6.5) 12.45 で入った JSON:API リソースを使っている 12.68.0 / 13.27.0
Laravel AI SDK の SSRF Moderate(5.3) 1.0.0 の Vercel か AG-UI アダプタを信頼できないクライアントに公開している 1.0.1
同記事:Laravel MCP の OAuth リダイレクト検証 Low 0.9.6 未満か 1.0.0 で、設定による 0.9.6 / 1.0.1
NativePHP for Mobile の7件 High 5件(7.4〜8.1)、Moderate 2件 nativephp/mobile の全系列(1.x と 2.x は EOL で修正なし)。多くは Web ビューで JavaScript を実行できる攻撃者が前提 4.6.1 / 3.3.11、nativephp/mobile-file 1.0.3

PHP 本体:未認証の RCE が2件

10月21日に PHP 8.5.12、8.4.27、8.3.36、8.2.35 が出た。修正された脆弱性は2件で、どちらも未認証のリモートコード実行につながる critical(CVSS 4.0 で 9.2)。PHP チームは完全な緩和策は無いとして即時の更新を求めている。前日に「翌日 14:00 UTC に出す」という予告があり、Forge と Cloud も同時に対応を告知していた。

  • CVE-2026-109618(OOM による破損):メモリ不足の処理が、リクエストの内部データが途中までしか初期化されていない時点で PHP を中断し、後片付けがその不正なデータを正しいものとして扱う。
  • CVE-2026-109619(残留するグローバル状態):リクエストが持つ内部状態がメモリ解放後も残り、同じワーカーの次のリクエストが初期化前にそれを使う。

mod_php が最も危険で、アプリのコードが走る前に未認証のリクエストから発火する実証がある。PHP 8.1 以前は PHP プロジェクトからの更新は無いが、深刻度を考えて 7.4、8.0、8.1 向けの最小限のソースバックポートがブランチとして公開された。有志が維持する rpms.remirepo.net は当日に更新されないので、ディストリビューションのパッケージかソースビルドへ切り替えるよう案内されている。

Laravel の各プラットフォームの状況は次のとおり(10月21日 23:26 UTC の時点)。Cloud は稼働中の全アプリをその場でパッチし、再デプロイは不要。Forge は各サーバーで Update PHP を押す手動更新で、Ubuntu 20.04 以前のサーバーは更新できないので新しいサーバーへ移す。Vapor はネイティブランタイムなら再デプロイ、Docker イメージなら新規デプロイが要る。Envoyer は復旧済みだがヘルスチェックは未復旧。

すぐ更新できない場合の緩和策も列挙されているが、PHP チームは「安全にはならない」としている。memory_limit=-1 をアプリから上書きできない場所に置く、post_max_size を下げる、session.upload_progress.enabled=0 にして全ワーカーを再起動する、8.5 なら fatal_error_backtraces=0 にする、など。

PHP Foundation の Volker Dusch へのインタビューは、発見から公開までの経緯。OpenAI の Patch the Planet で PHP の監査に入った Calif が Docker で動く最小の実証を持ち込み、OpenAI と Wiz も同じ根本原因の別経路を報告した。ディストリビューションには事前に修正を渡し、Cloudflare などの WAF ベンダーとも公開前にルールを出せるよう連携したが、Chainguard が公開差し止め(embargo)の取り決めを破り、協調公開の6時間以上前にパッチ済みイメージを出した。Volker は、PHP Foundation が常勤で雇う Ilija、Arnaud、Jakub が昼夜 C コードの修正に当たったことを挙げ、Foundation への支援を呼びかけている。

出典:https://laravel-news.com/php-critical-rce-security-releases / https://laravel-news.com/volker-dusch-php-rce-disclosure

Laravel 本体:token ガードの認証バイパス

Illuminate\Auth\TokenGuard に critical の認証バイパスがあった(GHSA-f9x3-7vq9-cph6、CVSS 9.2)。MySQL と MariaDB で、未認証の攻撃者が既存ユーザーの API トークンを知らずにそのユーザーとして認証できた。文字列でない細工した資格情報が認証のルックアップで誤って扱われ、クエリが意図しないユーザーに一致していた。修正は user() と validate() に is_string() の確認を足したもの。

if (is_string($token) && ! empty($token)) {

対象は token ドライバだけで、Sanctum と Passport は独自のガードドライバを登録しており告知には含まれない。修正版の 12.68.0 と 13.29.0 は8月25日に出ているので、それ以降に composer update したアプリは対応済み。告知は全ての 12.68.0 未満を影響範囲とし、パッチは 12.x と 13.x にしか無い。

同じ日に公開された2件目(GHSA-5wcm-3v2w-8362、CVSS 3.1 で 6.5)は、12.45 で入った JSON:API リソースで、読み取り専用のエンドポイントへのリクエストが関連テーブルに対する破壊的な操作を起こせるもの。修正は 12.68.0 と 13.27.0。記事が勧める手順は3つで、laravel/framework の更新、config/auth.php に token ドライバがあるかの確認、composer audit の実行。

出典:https://laravel-news.com/laravel-token-guard-auth-bypass

Laravel AI SDK と Laravel MCP:1.0 直後の修正

9月に 1.0 になった2つの公式パッケージに、それぞれ告知が出た。公開は日本時間で10月1日。どちらも CVE は未割り当てなので、CVE に頼るスキャナは拾わない可能性がある。

  • Laravel AI SDK 1.0.0 の SSRF(Moderate、5.3):Vercel AI SDK アダプタと AG-UI アダプタが、クライアントから受けたファイルパートの URL を検証せずに取得していた。チャットエンドポイントを呼べる人が、サーバーにクラウドのメタデータサービスやローカルホストへ GET を送らせ、応答をモデルの返答経由で読めた。両アダプタは 1.0 で初めて入ったので 0.x は無関係。1.0.1 で http と https 以外の拒否、ループバックやプライベートアドレスの遮断、リダイレクトの各ホップの検証と接続先の固定が入った。
  • Laravel MCP の OAuth リダイレクト検証(Low):OAuth フローの途中で認証済みユーザーを意図しない先へ送れ、設定によっては認可コードやトークンを取られる。ユーザーの操作が要る。0.9.6 と 1.0.1 で修正。
composer update laravel/ai laravel/mcp

出典:https://laravel-news.com/laravel-ai-mcp-security-advisories

NativePHP for Mobile:初の告知が7件

NativePHP が初めてのセキュリティ告知を7件まとめて出した。すべて nativephp/mobile 4.6.1 と 3.3.11、プラグイン nativephp/mobile-file 1.0.3 で修正されている。5件が High(7.4〜8.1)。多くは Web ビューとネイティブ側をつなぐ内部ルート /_native/api/* にミドルウェアが無く、入力を検査していなかったことに由来する。攻撃者がアプリの Web ビューで JavaScript を動かせること(XSS や侵害された外部スクリプト)が前提のものが多く、攻撃の複雑さは高いと評価されている。

パッケージを上げるだけではユーザーは守られない。NativePHP アプリは PHP を同梱するので、同じ週の PHP 本体の修正も含めて、再ビルドしてストアに出し直すまでが対応になる。

  1. nativephp/mobile を 4.6.1 へ(3.x 系は 3.3.11)。
  2. 4.x では nativephp/mobile-file をアンインストールする。コア側に File.Move と File.Copy が入り、プラグイン版が修正済みのものを上書きしうる。3.x ではプラグインを 1.0.3 へ。
  3. php artisan native:install --force でネイティブプロジェクトを再生成し、ビルドする。
  4. App Store と Google Play に新しいビルドを提出する。

挙動が変わる修正が2つある。/_native/api/events 経由で発火するイベントは DispatchableFromNative の実装が必須になり、非同期タスクのペイロードはアプリキーで暗号化され、認証される(アプリキーの無いアプリは非同期タスクを発火できない)。1.x と 2.x は EOL で修正されない。6件は事前の非公開報告が無く、RedEye Security の記事のあとに実証つきで公開されたと NativePHP は書いている。配布する PHP バイナリも修正版で再ビルドされ、ハッシュと署名の検証がツール側に入った(nativephp/desktop 2.4.0、nativephp/mobile 3.3.11、4.7.0 は後日)。

出典:https://laravel-news.com/nativephp-mobile-security-advisories

注目パッケージ

パッケージ早見表(15本)

気になった行だけ、下の該当セクションか出典リンクへ。

セクション パッケージ 何ができるか
公式 Laravel AI SDK 1.1 Agent Skills(SKILL.md)をエージェントに読ませる。Cohere のテキスト生成、組み込みツールの承認必須化。既定モデルが変わる
公式 Inertia.js v3.8 SSR でブラウザ専用コンポーネントを後から描く WhenMounted、64bit 整数を BigInt で受ける
公式 Inertia.js v3.9 バックグラウンドのタブでポーリングを止める background: 'pause'、履歴復元を見分ける navigate の type
公式 Filament v5.8 折りたたみセクションやタブの中身を、表示されるまで描画しない deferLoading()
AI Synapse AI SDK のエージェントを一覧し、ブラウザで対話してツール呼び出しとトークン数を記録する開発用ダッシュボード
AI BashBox AI エージェントが生成した bash を、proc_open を使わず純 PHP の仮想 FS で実行する
AI Laravel URL AI Transformer v2 URL 群を取得して AI に渡し、JSON-LD や構造化データを DB に保存する(Spatie)
AI LayaPHP 自前ホストの分類モデル Laya に、はい/いいえ、選択、スコアの問いを投げる PHP クライアント
AI VMPal Apple silicon 上の VM を Claude Code などのエージェントに丸ごと渡す(TablePlus のチーム)
AI Sloppy AI が書いた PHP に残りがちな握りつぶした例外や N+1 を、モデル無しで静的解析する
業務機能 Grant ロールと権限を PHP の enum で定義し、Gate 経由で can() と @can から使う
業務機能 Postcodes for Laravel 英国の郵便番号を検索し、行政区域や半径検索、存在確認のバリデーションを行う
業務機能 Screenshot HTML、Blade、URL を Node.js 無しで Chrome から PNG、JPEG、PDF にする
アプリ Skuul 3.0 Laravel 13 と Livewire 4 で書き直された、オープンソースの学校管理システム
アプリ Pollora Laravel アプリの中で WordPress を動かし、ルーティングと描画を Laravel 側で行う

公式リリースとFilament

  • Laravel AI SDK 1.1:1.0 以来初のマイナーで、目玉は Agent Skills 対応。resources/skills/<name>/SKILL.md に置いた指示書を、HasSkills を実装したエージェントが LoadSkill ツール1つで必要なときだけ読む。供給元はディレクトリ、PHP で組んだ Skill、クロージャのどれでもよく、.agents/skills を指せばコーディングエージェントと同じスキルを共有できる。ほかに Cohere のテキスト生成、組み込みツールへの requireApproval()、フェイルオーバー先が対応しないプロバイダツールを外して続行する変更。
// Agent と HasSkills を実装したエージェントの抜粋
public function skills(): iterable
{
    return [resource_path('skills'), base_path('.agents/skills')];
}

上げるときの注意:既定モデルが更新された(Anthropic は claude-sonnet-5-5、Gemini は gemini-3.8-flash)。config/ai.php でモデルを固定していないアプリは、上げた時点で新しいモデルを使う。

  • Inertia.js v3.8:WhenMounted は、window を読むコンポーネントを SSR で落とさないためのコンポーネント。サーバーでは fallback を描き、ブラウザでマウントしたあとに子を描く。React では子を関数で渡す。BigInt 対応は opt-in で、inertia-laravel v3.5.0 が要る。64bit の ID がブラウザの JSON パースで丸められる問題への対処で、preserve_big_integers か ->preserveBigIntegers() で有効にする。<Form> のエラーコールバック転送、楽観的更新の応答順、デプロイをまたいだ戻る操作の修正も入った。

  • Inertia.js v3.9:usePoll() と router.poll() に background オプションが付いた。既定の throttle はバックグラウンドのタブで10回に1回動き、pause は表示されるまで止め、continue は間隔どおり続ける。keepAlive は非推奨になった。navigate イベントの detail.type が initial、visit、history を返すので、戻るボタンで復元されたページだけ props を取り直せる。clearHistory() がプリフェッチキャッシュも消すようになり、ログアウト後に戻るボタンで保護ページが見える問題が直った。

usePoll(5000, {}, { background: 'pause' })
  • Filament v5.8:Schema に deferLoading() が付いた。schema() に配列ではなく Schema オブジェクトを渡すと、その子スキーマはビューポートに入った時点で別リクエストで描画される。折りたたんだセクション、非アクティブなタブ、閉じたリピーター項目が対象。ほかに、グループ化をセッションに保存する persistGroupInSession()、トレイト名を接尾辞にしたライフサイクルフック、自前のコンポーネントで使える二要素チャレンジ MultiFactorChallenge、エクスポートの getDownloader()。Boost のガイドラインは Agent Skill になった。
Section::make('Customer details')
    ->key('customerDetails')
    ->schema(
        Schema::make()
            ->components([TextInput::make('name'), TextInput::make('email')->email()])
            ->deferLoading(),
    )
    ->collapsed()

AIエージェントの道具

  • Synapse:Laravel AI SDK で作ったエージェントの開発用ダッシュボード(Redberry 製)。app/Ai/Agents と app/Agents の Agent 実装を登録なしで一覧し、ブラウザのチャット画面を付ける。返答ごとにツール呼び出しの引数と結果、トークン数(キャッシュと reasoning を含む)、例外のスタックトレースを記録し、モデルセレクタで別モデルを試せる。会話は自身のテーブルに保存する。composer require redberry/synapse --dev と synapse:install で /synapse に出る。

  • BashBox:純 PHP で書かれた bash インタープリタ(Pushpak Chhajed 製)。AI エージェントが生成したスクリプトを proc_open も exec も呼ばずに実行する。各コマンドは PHP クラスとして実装され、仮想ファイルシステムの上で動き、grep、sed、find など58コマンドを同梱する。FS は既定でメモリ内、実ディレクトリを読んで書き込みだけメモリに持つ OverlayFs もある。Limits でコマンド数やループ回数を制限でき、ネットワークは NetworkConfig で許可した URL にしか出ない。PHP 8.4 以上、依存なし、MIT、最新は v0.0.3。

$bash = new Bash(new BashOptions(
    initialFiles: ['/home/user/app.log' => "INFO boot\nERROR db timeout\n"],
));

$bash->exec('grep ERROR app.log')->stdout; // "ERROR db timeout\n"
  • Laravel URL AI Transformer v2:URL の一覧を取得して各ページの本文を AI に渡し、結果を transformation_results テーブルに保存する Spatie のパッケージ。自サイトの JSON-LD 生成や商品ページからの項目抽出が用途。v2 では各トランスフォーマーが Laravel AI SDK のエージェントになった(v1 は Prism)。instructions() だけで書け、HasStructuredOutput を実装すればスキーマで検証した JSON が入る。transform-urls --url="*/blog/*" で一部だけ再実行できる。PHP 8.4、Laravel 12 か 13。

  • LayaPHP:自前ホストできる分類モデル Laya の PHP クライアント(Marc Reichel 製)。Laya は会話を生成せず、ラベルの選択、スコア、はい/いいえの確率だけを返すモデルで、TypeSafe の Jev と同じ3種の問いを受ける。predict() に Question::yesNo()、choice()、score() を複数渡して1リクエストで答えを得、answerConfidence がしきい値未満なら人のレビューへ回す。PHP 8.4 以上と PSR-18 クライアント。Docker Compose で laya-serve を起動し、初回に約 1 GB の重みをダウンロードする。AI SDK 1.1 では typesafe ドライバの url を Laya に向けられる。

  • VMPal:TablePlus のチームによる macOS 用の VM アプリ。Apple silicon 上で macOS、Windows、Linux の VM を動かし、1台を Claude Code、Codex、Grok CLI、Gemini CLI に渡す。エージェントは内蔵の MCP サーバー経由で画面、マウス、キーボード、管理者権限を持ち、アクセシビリティや画面収録の許可ダイアログも出ない。制御は既定で無効で VM ごとに有効にし、スナップショットで巻き戻せる。macOS 26 以降が必要。買い切りで、発売記念の半額が個人 $34.50。

  • Sloppy:AI コーディングエージェントが PHP に残しがちな保守上の問題を見る静的解析ツール(Vacuum と Difflock の作者 heyosseus 製)。モデルも API キーも使わない。空の catch(SL107)、中身が未実装のまま残ったメソッド、Model::all()->where() のようにコレクションで絞る書き方(SL205)、N+1、ループ内のクエリを指摘する。sloppy agents install で CLAUDE.md の規約とフックが入り、編集ごとに指摘をエージェントへ返す。sloppy diff main --merge-base や sloppy review main --merge-base で、分岐以降の新しい指摘だけを見る。PHP 8.3 以上、Laravel 12 か 13、MIT。

composer require --dev heyosseus/sloppy
vendor/bin/sloppy review main --merge-base

認可、住所検索、スクリーンショット

  • Grant:ロールと権限を PHP の enum で定義する認可パッケージ(Pushpak Chhajed 製)。権限の enum は Ability を、ロールの enum は Role を実装し、割り当てだけを DB に持つ。判定は Gate を通るので既存の can() や @can がそのまま効く。on: $team でモデルをスコープにした割り当てができ、ポリシーのメソッドに #[Requires(Permission::EditPosts)] を付けて権限チェックを前段に置ける。ロールのミドルウェアや @role は無い。enum の値を変えたら grant:sync で付け替える。PHP 8.3 以上、Laravel 12 か 13。
$user->grant(Role::Editor);
$user->grant(Role::Viewer, on: $team);

$user->can(Permission::ViewReports, $team);
  • Postcodes for Laravel:英国の GB Postcodes API のクライアント(Joshua Dovey 製)。Postcodes::get('M16 0RA') が型付きの Postcode を返し、緯度経度、自治体、選挙区などの行政区域と人口推計が取れる。autocomplete() で部分入力の候補、radius() で半径検索、within() で GeoJSON のポリゴン内検索。ValidPostcode::unit() は形式だけでなく実在も検証するルールで、形式が不正なら API を呼ばずに弾き、API エラー時は既定では通し、strict() を付けると弾く。成功応答は1日キャッシュ。PHP 8.2 以上、Laravel 12 か 13。国内案件に出番は少ないが、英国向けの EC や物流なら住所まわりを丸ごと任せられる。

  • Screenshot:HTML、Blade ビュー、URL を PNG、JPEG、PDF にするパッケージ(Steve Bauman 製)。PHP から Chrome を直接起動して制御するので Node.js も Puppeteer も要らない。Screenshot::view()->size()->jpeg(quality: 85)->store(path:, disk: 's3') と書き、element('#certificate') で要素だけ切り出し、pdf()->paper(PaperSize::A4) で印刷する。JavaScript は既定で無効。screenshot:install が Chrome for Testing をダウンロードし、既存の Chrome は SCREENSHOT_CHROME_PATH で指す。Laravel 12 は PHP 8.2 以上、Laravel 13 は PHP 8.3 以上。Screenshot::fake() あり。

学校管理とWordPress

  • Skuul 3.0:オープンソースの学校管理システム(Marvellous Ifezue 製、MIT)。3.0 は Laravel 13 と Livewire 4 での書き直しで、10月16日に出た。複数の組織とキャンパス、入学管理、成績簿、承認つきの成績公開、通知表、会計期間つきの台帳、バックグラウンドの CSV と PDF 出力、暗号化ダンプの定期バックアップまで入る。認可は Spatie Permission のチームスコープ。制約も明記されていて、公開 REST API は無く、Stripe の決済ルートは未接続。V2 からの移行では、旧形式の試験結果と採点方式、コースに紐付かない成績簿が削除され、ロールバックでも戻らない。PHP 8.4.1 以上、Node.js 24、MySQL 8、Redis。

  • Pollora:Laravel アプリの中で WordPress を動かすフレームワーク(MIT)。WordPress 本体は Composer で public/cms に入り、Laravel の起動時に読み込まれる。フロントは Laravel のルーターと Blade が描き、/cms/wp-admin/ の管理画面とプラグインはそのまま動く。Route::wp('single', ...) のように条件分岐タグでルートを書き、一致しなければテンプレート階層に従って Blade を探す。投稿タイプやフックは #[PostType] などの PHP 属性で宣言する。Pollora 公式の比較表は、標準の WordPress にテーマを作るなら Sage、既存サイトに Laravel の部品を足すなら Acorn、読むだけなら Corcel と使い分けを示し、Pollora 自身を若いプロジェクトとしている。

Route::wp('template', 'landing', function () {
    return view('page.landing', ['features' => Feature::all()]);
});

エコシステムの動き

  • PHP News が開設された:Laravel News の Eric Barnes が Matt Stauffer と、PHP 全般を扱う PHP News を立ち上げた。PHP 本体とパッケージのニュース、リリースとセキュリティ修正、全オープン RFC を平易に要約する RFC Watch、サポート中バージョンと EOL の一覧、イベントカレンダー、週刊ニュースレターがある。Laravel News は Laravel に絞ったまま続く。今月の PHP RCE の続報(Chainguard による公開差し止め違反、session.upload_progress の追加告知)はすでに PHP News 側に出ている。
  • Filament のプラグインディレクトリに Plumb のスコアが載った:Filament のプラグインが1,000を超えたのに合わせ、Kevin Ullyott 製の Plumb によるスコアがプラグインディレクトリの一覧と詳細に表示されるようになった。Plumb は PHP パッケージをセキュリティ、保守、エコシステムの3分類15項目(GitHub Actions が SHA で固定されているか、など)で自動採点する。各プラグインの「Package health」でどの項目が通ってどれが落ちたかを読める。公開プラグインは自動で採点済みで、Filament 外のパッケージも Packagist の約17万件を追跡している。

今月のトレンド

  • 読み取りレプリカ運用の穴が、1か月で順に塞がれた。 9月までの sticky は同じリクエストの中でしか効かず、キューワーカーでは逆に効きすぎていた。13.36 でワーカーがジョブごとに sticky を捨てられるようになり、13.38 でリクエストをまたいだ書き込み直後の読み取りを PreventStaleDatabaseReads が書き込み側へ寄せ、リレーションまで含めて書き込み側で読む usingWritePdo() が付いた。DB::olap() も含めて、どれも Jack Bayliss の PR で、解説記事が全部そろっている。
  • 告知ごとに、やることが違った。 PHP 本体は「全構成が影響、緩和策で安全にはならない」で、Laravel の token ガードは「8月25日の修正版を入れていれば済み」。AI SDK と MCP は CVE 無しで、CVE 頼みのスキャナが拾わないことがある。NativePHP はパッケージ更新だけでは足りず再ビルドとストア提出が要る。修正版の番号を見るだけでは足りず、4つとも対応が違う。
  • AI エージェントを使う側から、観測して囲う側へ。 エージェントの中身を見る Synapse、生成したスクリプトを隔離して走らせる BashBox、残したコードを lint する Sloppy、VM を1台渡して本体を触らせない VMPal。先月の Vet(依存の差分を見る)と同じ流れで、AI SDK 1.1 と Filament が Agent Skills に乗ったのも、エージェントに読ませる文脈を絞る方向だ。

まとめ

今月分で手を付ける順。

  1. 本番サーバーの php -v。使っている系列の修正版(8.5.12、8.4.27、8.3.36、8.2.35)より古ければ、ほかの何より先に上げる。
  2. composer audit。config/auth.php に token ドライバがあるアプリは、laravel/framework が 12.68.0 か 13.29.0 以上かを見る。
  3. AI SDK と MCP の 1.0.0 は 1.0.1 へ。
  4. NativePHP for Mobile は 4.6.1 に上げて再ビルドし、ストアに出すところまで。
  5. レプリカを使っているなら、PreventStaleDatabaseReads を web に足して、既定の2秒から試す。

詳細は各出典リンクから。来月も続ける。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?