Install
$ agentstack add skill-hlsitechio-claude-skills-security-svelte-sveltekit-security ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Svelte / SvelteKit Security Audit
Audit Svelte and SvelteKit apps. Covers Svelte 3/4/5 and SvelteKit 1/2.
When this skill applies
- Reviewing Svelte components for XSS sinks
- Auditing SvelteKit server load functions, form actions,
+server.tsroutes - Reviewing
hooks.server.tsmiddleware - Checking
$envmodule usage for accidental leaks - Auditing form actions for auth and CSRF
Workflow
Follow ../_shared/audit-workflow.md.
Phase 1: Stack detection
grep -E '"(svelte|@sveltejs/kit)":' package.json
find . -name 'svelte.config.*' -name '*.svelte' -not -path '*/node_modules/*' | head
Phase 2: Inventory
# XSS sinks
grep -rn '@html' src/ 2>/dev/null
# Server load functions (run on server)
find src -name '+page.server.ts' -o -name '+layout.server.ts' 2>/dev/null
# Universal load (runs both server and client)
find src -name '+page.ts' -o -name '+layout.ts' 2>/dev/null
# Endpoint handlers
find src -name '+server.ts' 2>/dev/null
# Form actions
grep -rn 'export const actions' src/ 2>/dev/null
# Hooks (middleware)
find src -name 'hooks.server.ts' -o -name 'hooks.client.ts' 2>/dev/null
# Env modules
grep -rn '\$env/static/private\|\$env/static/public\|\$env/dynamic/private\|\$env/dynamic/public' src/ 2>/dev/null
Phase 3: Detection — the checks
{@html} XSS
- SVK-XSS-1 Every
{@html foo}reviewed. Same sink asdangerouslySetInnerHTML. Sanitize with DOMPurify or use a safe Markdown renderer.
{@html post.content}
import DOMPurify from 'isomorphic-dompurify';
$: clean = DOMPurify.sanitize(post.content);
{@html clean}
Server vs universal load functions
+page.server.ts runs ONLY on server. +page.ts runs on server during SSR AND on client during navigation.
- SVK-LOAD-1 Code in
+page.ts(universal load) must not import server-only modules (database, secret env). Use$env/static/private→ will fail at build if a universal load imports it. - SVK-LOAD-2 Data returned from server load serializes to the client during hydration. Don't return secrets in the load function's return object.
- SVK-LOAD-3 Universal load returning data fetched via authenticated API: the fetch on the client uses the user's cookies (good), but the server-side fetch during SSR uses no cookies by default — need to forward.
// +page.ts (universal)
export const load = async ({ fetch, params }) => {
// SvelteKit's fetch forwards cookies during SSR — verify
const res = await fetch(`/api/posts/${params.id}`);
return { post: await res.json() };
};
+server.ts route handlers
API endpoints. Same scrutiny as any REST endpoint.
- SVK-API-1 Every handler checks auth. SvelteKit provides no implicit auth.
- SVK-API-2 Input validation via Zod / valibot on request body and URL params.
- SVK-API-3 CORS configured if endpoint is cross-origin reachable.
- SVK-API-4 No accidental data exposure — return only what caller is authorized to see.
// +server.ts
import { json, error } from '@sveltejs/kit';
export const GET = async ({ locals, params }) => {
if (!locals.user) throw error(401);
const item = await db.items.findFirst({
where: { id: params.id, tenantId: locals.user.tenantId },
});
if (!item) throw error(404);
return json(item);
};
Form actions
Form actions are POST endpoints generated from actions exports in +page.server.ts.
- SVK-FA-1 Every action checks auth.
- SVK-FA-2 Input validated; don't trust
formData.get(...)directly. - SVK-FA-3 Authz on the resource being mutated.
- SVK-FA-4 Actions return data that ships to the client — don't return secrets or other-user data.
- SVK-FA-5 SvelteKit has built-in CSRF protection (Origin header check); verify it's not disabled via
csrf: falsein svelte.config.js.
// +page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import { z } from 'zod';
const DeleteSchema = z.object({ postId: z.string().uuid() });
export const actions = {
delete: async ({ request, locals }) => {
if (!locals.user) throw redirect(303, '/login');
const formData = await request.formData();
const parsed = DeleteSchema.safeParse({ postId: formData.get('postId') });
if (!parsed.success) return fail(400, { error: 'Invalid input' });
const post = await db.posts.findFirst({ where: { id: parsed.data.postId } });
if (!post || post.authorId !== locals.user.id) throw error(403);
await db.posts.delete({ where: { id: post.id } });
return { success: true };
},
};
hooks.server.ts
Runs before every request. Place for auth resolution + global headers.
- SVK-HK-1 Auth resolution in
handlesetsevent.locals.user— every downstream load/action/route should uselocals.user, not re-read cookies. - SVK-HK-2 Global headers (CSP, HSTS, etc.) set here via
event.setHeadersor response transformation. - SVK-HK-3 Errors caught in
handleErrordon't leak details in client response; log server-side.
$env module usage
SvelteKit splits env vars into 4 modules:
| Module | Where | Risk | |--------|-------|------| | $env/static/private | Server only, bundled at build | Safe | | $env/static/public | Both (must start with PUBLIC_) | Public, treat as ships to client | | $env/dynamic/private | Server only, at runtime | Safe | | $env/dynamic/public | Both (must start with PUBLIC_) | Public |
- SVK-ENV-1 Server-only secrets imported from
$env/static/privateor$env/dynamic/private. NeverPUBLIC_*. - SVK-ENV-2 Variables prefixed with
PUBLIC_(required for$env/*/public) are NOT secrets. Same trap asNEXT_PUBLIC_. - SVK-ENV-3 SvelteKit refuses to bundle
$env/static/privateimports into client code — build will fail. So the prefix model is enforced. But check that no one triedPUBLIC_DATABASE_URLto "make it work".
Svelte 5 runes
Svelte 5 introduced runes ($state, $derived, $effect). Security implications:
- SVK-R5-1
$statesynced to localStorage / sessionStorage doesn't store secrets. - SVK-R5-2
$effectrunning on the client doesn't accidentally execute server-side intent (legacy code mixing Svelte 4onMountwith$effect).
Dependencies
- SVK-DEP-1 SvelteKit version current. SvelteKit 1.x had a CSRF disable issue (CVE-2023-44389 class); ensure 2.x or patched 1.x.
- SVK-DEP-2 Adapters (
@sveltejs/adapter-node,@sveltejs/adapter-vercel, etc.) current.
Phase 4: Triage
Critical: {@html} with raw user input; form action without auth; data with secrets returned from load.
Phase 5: Report
Use ../_shared/findings-schema.md. Prefix IDs with SVK-.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: hlsitechio
- Source: hlsitechio/claude-skills-security
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.