Skip to content

SEC005 · Shared runes-state import on the server

Severity: warning · Category: security

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.

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.

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:

+page.server.ts
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.