Skip to content

SEC003 · Handler writes imported state

Severity: critical · Category: security

Flags writes to an imported binding from inside a server-executed handler — load, a form action, a +server HTTP handler, or a hooks.server handler: property assignment (state.user = …), increment/delete, and .set(...) / .update(...) calls. Universal +page.ts/+layout.ts load functions are included — they run on the server during SSR.

Reads, other method calls (logger.info(…)), and writes to local variables are not flagged. Nor are .set()/.update() calls on imports from installed packages or anything resolving to src/lib/server — the directory-entrypoint import (import { db } from '$lib/server') as well as anything under src/lib/server/** (database/KV clients — e.g. Drizzle’s db.update(...).set(...)) — that exemption is applied to the resolved path, so it holds however the module is imported, whether via the $lib/server/ alias or a relative path (../../lib/server/db), and a specifier whose .. segments escape the project root is conservatively never treated as repo-local state; those are persistence, not shared module state.

This is the pattern SvelteKit’s state-management docs mark “NEVER DO THIS”. The server is one long-lived process shared by every user: module state written during Alice’s request is still there when Bob’s request arrives — Bob can be served Alice’s data. It works perfectly in single-user dev and corrupts silently in production.

Return the data instead of storing it:

+page.ts
import { user } from '$lib/user';
export async function load({ fetch }) {
const response = await fetch('/api/user');
user.set(await response.json()); // ❌ shared across ALL requests on the server
return { user: await response.json() }; // ✅ per-request page data
}

Per-user data belongs in cookies/locals plus a database; share loaded data with components via page.data or the context API.