コンテンツにスキップ

SEC003 · handler から import した状態への書き込み

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

サーバーで実行される handler(load、form action、+server の HTTP handler、hooks.server の handler)の内部から、import した binding への書き込みを検出します — プロパティ代入(state.user = …)、インクリメント/delete.set(...) / .update(...) 呼び出しが対象です。universal な +page.ts/+layout.ts の load も対象です — SSR 時はサーバーで実行されるためです。

読み取り、その他のメソッド呼び出し(logger.info(…))、ローカル変数への書き込みは検出対象外です。インストール済みパッケージや、解決先が src/lib/server になる import — ディレクトリエントリポイントの import(import { db } from '$lib/server')および src/lib/server/** 配下 — への .set()/.update()(Drizzle の db.update(...).set(...) など DB/KV クライアント)も対象外です — この除外は解決後のパスに対して適用されるため、$lib/server/ alias 経由でも相対パス(../../lib/server/db)経由でも同様に働き、.. がプロジェクトルートの外へ抜ける specifier は保守的にリポジトリ内の共有状態として扱われません。永続化であり共有状態への書き込みではないためです。

SvelteKit の状態管理ドキュメントが「NEVER DO THIS」と明記するパターンです。サーバーは全ユーザーが共有する長寿命の1プロセスです: Alice のリクエスト中に書き込まれたモジュール状態は、Bob のリクエストが来たときもそこに残っています — Bob に Alice のデータが配信され得ます。開発中(シングルユーザー)では完璧に動き、本番で静かに壊れます。

保存せずにデータを返します:

+page.ts
import { user } from '$lib/user';
export async function load({ fetch }) {
const response = await fetch('/api/user');
user.set(await response.json()); // ❌ サーバー上では全リクエストで共有される
return { user: await response.json() }; // ✅ リクエストごとの page data
}

ユーザー別データは cookies/locals + データベースへ。load したデータは page.data か context API でコンポーネントに渡します。