SEC005 · Shared runes-state import on the server
Severity: warning · Category: security
What it checks
Section titled “What it checks”Flags an import in a SvelteKit route/hooks file whose specifier resolves to a repo-local .svelte.ts/.svelte.js module with module-scope $state (a top-level $state(...) declaration, or a module-scope instance of a class with $state fields). Two flavours:
- the server code mutates the imported state (outside handlers — handler writes are reported by SEC003 as critical), or
- the import is read-only — on the server the state is still one shared instance that keeps its boot-time value.
Direct imports only ($lib/… and relative specifiers); import type is excluded. Client-only usage of such modules — the idiomatic shared-store pattern — is fine and never flagged; only imports from server-executed files are.
An extensionless ….svelte specifier canonicalises to ….svelte.ts for resolution purposes, so importing the component X.svelte while a $state-holding X.svelte.ts sibling exists can misattribute a finding to the component import — a rare naming coincidence.
Why it matters
Section titled “Why it matters”On the browser each user gets their own module instance; on the server there is exactly one, shared by every request. If it ever holds per-user data, users see each other’s data; even if it doesn’t, server reads see a stale boot-time value rather than what the current user’s client sees.
How to fix
Section titled “How to fix”Don’t reach for shared module state in server-executed code — return data from load and pass it via page.data or the context API:
import { quizState } from '$lib/quiz.svelte.js'; // ❌ one instance for all users on the server
export async function load({ locals }) { return { bookmarks: await db.bookmarksFor(locals.user) }; // ✅}If the module is genuinely client-only, restructure so server files don’t import it — or, if the import is deliberate and safe, add // svelte-vitals-disable-next-line SEC005 above it.