コンテンツにスキップ
svelte-vitals
日本語
Esc
移動開く⌘Jプレビュー
このページの内容

security/shared-state-import · サーバーからの共有 runes 状態の import

server / universal ファイルが、モジュールスコープに $state を持つ .svelte.ts を import しています。サーバープロセスでは 1 つのインスタンスを全リクエストで共有します。

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

チェック内容

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

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

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

検出しないもの: 同じファイルで ssr = false を export している universal な +page.ts/+layout.ts。SvelteKit の状態管理ドキュメント: 「SSR を使っていないなら、あるユーザーのデータが別のユーザーに漏れる心配はありません」。そのファイルの load はサーバーで実行されないため、漏れる先の共有サーバーインスタンスがそもそも存在しません。この除外は同一ファイルに限られるため、ssr の値に関わらず常にサーバーで実行される +page.server.ts は引き続き検出されます。

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

なぜ重要か

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

修正方法

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

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 security/shared-state-import を書いてください。

モードによる違い

ありません。このルールが読むのは同じ .svelte / .ts のソースファイルなので、CLI、Vite プラグインのビルド、ライブダッシュボードの静的ベースラインのいずれでも結果は同一で、レンダリング済み HTML の解析で再評価されることもありません。--route で実行範囲を絞ると、このルールは動きません。コンポーネントスコープのルールには、検出を紐づけるルートが無いためです。

無効化

個別に抑制するには、対象行の直前に <!-- svelte-vitals-disable-next-line security/shared-state-import --> を置きます。ルールごと無効化するには、次のように設定します。

export default {
  rules: {
    'security/shared-state-import': 'off'
  }
};