コンテンツにスキップ

SEC005 · サーバーからの共有 runes 状態の import

重大度: warning · カテゴリ: security

SvelteKit のルート/フックファイル内の import のうち、参照先がリポジトリ内の .svelte.ts/.svelte.js モジュールで、モジュールスコープの $state(トップレベルの $state(...) 宣言、または $state フィールドを持つクラスのモジュールスコープインスタンス)を持つものを検出します。2つのフレーバーがあります:

  • サーバーコードがその状態を変異させている(handler 外での書き込み — handler 内の書き込みは SEC003 が critical で報告します)
  • 読み取り専用 — それでもサーバー上では起動時の値を保持し続ける全リクエスト共有の1インスタンスです

直接 import のみが対象です($lib/… と相対パス)。import type は対象外。こうしたモジュールのクライアント専用での利用 — 慣用的な共有ストアパターン — は正当であり、決して検出されません。サーバーで実行されるファイルからの import だけが対象です。

拡張子なしの ….svelte という specifier は解決の際に ….svelte.ts に正規化されるため、$state を持つ X.svelte.ts が同名で存在する場合、コンポーネント X.svelte の import に対して誤って検出結果が紐づくことがあります — 稀な命名の偶然です。

ブラウザではユーザーごとに独自のモジュールインスタンスが作られますが、サーバーでは全リクエスト共有のちょうど1つです。ユーザー別データを入れればユーザー間でデータが見え合い、入れなくてもサーバー側の読み取りは現在のユーザーのクライアントと一致しない起動時の古い値になります。

サーバーで実行されるコードで共有モジュール状態に頼らず、load からデータを返して page.data か context API で渡します:

+page.server.ts
import { quizState } from '$lib/quiz.svelte.js'; // ❌ サーバー上では全ユーザーで1インスタンス
export async function load({ locals }) {
return { bookmarks: await db.bookmarksFor(locals.user) }; // ✅
}

モジュールが本当にクライアント専用なら、server ファイルから import しない構造に変えます — import が意図的で安全なら、その直前に // svelte-vitals-disable-next-line SEC005 を書いてください。