Claude Cursor GitHub Copilot Skill

sveltekit-actions-load-security-review

Statically review SvelteKit form actions, load functions, hooks, and templates for CSRF origin-check bypass (checkOrigin/trustedOrigins), unauthenticated sensitive-data returns from load(), auth guards confined to +layout.server.js without an enforced parent()/hooks check, insecu

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vincentchuwaichow-vanguard-frontier-agentic-skills_frontend_sveltekit-actions-load-security-review-febe32a.zip · 13 KB
Part of vincentchuwaichow/vanguard-frontier-agentic — 293 skills

Install

skills CLI npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/sveltekit-actions-load-security-review
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
Git git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

SvelteKit Actions & Load Security Review

Purpose

Review SvelteKit form actions (export const actions), load() functions (+page.server.js/+page.js, +layout.server.js/+layout.js), +server.js endpoints, and template bindings for the concrete, documented defect classes that recur in SvelteKit apps: CSRF protection disabled or weakened via checkOrigin/trustedOrigins, load() returning sensitive data with no auth check on that exact path, an auth guard that lives only in a parent +layout.server.js and is silently skipped by a child page that never calls await parent() (or by client-side navigation that does not re-run the layout load), cookies set without httpOnly/secure/sameSite/path explicitly locked down, and unsanitized {@html} rendering of user-reachable input. This skill exists so the review stays anchored to these five documented, security-critical sinks instead of drifting into a general "SvelteKit code review" of routing, reactivity, or component design.

When to use

Use this skill when the user asks to:

  • review a SvelteKit form action (export const actions) or a load() function for authentication/authorization correctness,
  • assess whether svelte.config.js's csrf block (checkOrigin, trustedOrigins) is safely configured,
  • investigate whether an authenticated route is actually protected on every entry path (direct page load, client-side navigation, and the action itself),
  • assess whether a cookies.set() call or an {@html} binding is safe,
  • perform a pre-launch security review of a SvelteKit application's server-side data-loading and mutation surface.

Do not use this skill for:

  • general SvelteKit routing, reactivity ($state/$derived), or component-composition review with no security angle — use a SvelteKit architecture-focused skill instead,
  • a purely client-only Svelte component tree with no +page.server.js/+layout.server.js/+server.js/form-action code and no cookie or {@html} usage — there is no server-side sink in scope,
  • a bug that requires live traffic reproduction (a captured cross-site request, a live CSRF proof-of-concept, session-replay capture) to confirm exploitation — static analysis proves the structural risk, not that it has already been exploited in production.

Context7 Documentation Protocol

  • Resolve the library ID with resolve-library-id (matched result: /sveltejs/kit) before citing any CSRF-mechanism, cookie-default, or load()/auth claim.
  • /sveltejs/kit is SvelteKit's own repository (runtime source such as respond.js/cookie.js plus its documentation tree), so both source-level mechanics and prose guidance are queryable through query-docs. Use it to confirm exact runtime behavior — e.g., that csrf_check_origin only rejects same-origin-mismatched, form-content-type POST/PUT/PATCH/DELETE requests, that trustedOrigins: ['*'] fully disables the origin check regardless of checkOrigin, and that cookies.set() defaults httpOnly and secure to true (with secure relaxed only on plain-HTTP localhost) and sameSite to 'lax'.
  • Before flagging a +layout.server.js auth guard as insufficient, confirm via query-docs (or the official_docs URLs in this skill's metadata.json if Context7 is unavailable) that layout load() functions do not re-run on every child-route navigation and that layout/page load() functions run concurrently unless a child explicitly calls await parent() — this is the documented mechanism behind the risk, not an assumption.
  • Read package.json and svelte.config.js first to confirm the SvelteKit major version and adapter in use — cookie defaults (the path-required behavior) and CSRF option shape changed between SvelteKit 1 and 2; do not apply v2 requirements to a v1 codebase or vice versa.
  • If Context7 is unavailable, fall back to the official_docs URLs in this skill's metadata.json and label the claim documentation-based, unverified against current release.

Lean operating rules

  • CSRF-bypass, auth-leakage, and XSS findings default to HIGH severity. This is a security-scoped skill: do not downgrade a checkOrigin: false, a wildcard trustedOrigins, an unguarded sensitive load() return, or an untraced {@html} sanitizer gap to MEDIUM just because it has not been observed exploited yet — the risk is in the structure, not in whether someone has already hit it.
  • Trace every finding to a concrete file:line and a concrete data-flow path. A finding that says "this load() might leak data" or "this cookie might be insecure" without showing the specific cookies.get() call, the specific missing guard, or the specific cookies.set() options object is not a valid finding — it is a guess.
  • For every export function load() (or export const actions), determine whether an auth check happens inside that exact function (e.g., via a requireLogin()-style helper reading event.locals/cookies) before any sensitive data is fetched or returned. Do not accept "the parent layout checks auth" as sufficient unless the specific child load() under review actually calls await parent() and that call's result is checked, or hooks.server.js's handle function enforces the guard before any load() runs.
  • Do not approve a raw cookies.get(...) value being passed directly into a database call or trusted as an identity claim. A session/user identity must be resolved through a verifying helper (session-store lookup, signature check, requireLogin()) — a lookup with no verification step is not authentication, it is an unguarded read keyed on attacker-controlled input.
  • Check every cookies.set() call for explicit httpOnly, secure, sameSite, and path. Flag any call that sets httpOnly: false (or otherwise turns off a secure default) on a session/identity cookie as HIGH, and flag any call missing path as at least MEDIUM (SvelteKit requires an explicit path since v2 specifically to avoid ambiguous cookie scoping).
  • Do not approve an {@html} binding whose data source includes any user-reachable input (route params, query strings, request bodies, form-submitted content, third-party API responses that themselves echo user input) unless a named sanitizer call (e.g., DOMPurify.sanitize()) is visibly present on that exact data-flow path in the template expression. A sanitizer import existing elsewhere in the codebase does not clear this bar.
  • Treat checkOrigin: false and trustedOrigins: ['*'] as equally severe: both fully disable SvelteKit's CSRF origin check for cross-site form submissions, even though only one of them touches the literal checkOrigin key.
  • Never execute, build, or run application code, and never send live requests, as part of this review; this is a static-review skill (Read/Grep/Glob only).
  • Load only the reference needed for the concern in scope.

References

Load these only when needed:

  • Review workflow and findings contract — use for the step-by-step review procedure, the CSRF/auth/cookie/XSS decision tree, and the required output shape.
  • CSRF and auth-boundary review — load only when the review scope includes svelte.config.js's csrf block, a form action, or a load()/+layout.server.js/hooks.server.js auth-guard trace.
  • Cookies and {@html} review — load only when the review scope includes a cookies.set() call or an {@html} template binding.

Response minimum

Return, at minimum:

  • the form action(s), load() function(s), hooks.server.js handle, svelte.config.js csrf block, cookie operations, and/or {@html} bindings in scope,
  • ranked findings with file:line evidence, defect category (csrf-bypass, auth-leakage, auth-boundary, cookie-policy, or xss), the concrete data-flow trace (the exact checkOrigin/trustedOrigins value, the cookies.get()-to-sink path, the layout-to-child guard gap, the cookies.set() options object, or the {@html} origin-to-sink path), and a fix sketch matching SvelteKit's documented pattern,
  • for every {@html} finding, an explicit statement of whether a sanitizer call is present on the traced path — never approve on the assumption one exists elsewhere,
  • for every auth-boundary finding, an explicit statement of whether the guard is enforced in hooks.server.js (applies to every request) or only in a +layout.server.js/+page.server.js load() (applies only if that exact function runs and, for a layout, only if a child calls await parent()),
  • evidence level per finding (repo evidence, documentation-based, or inference), with structural risk findings explicitly labeled as structural risk, not as confirmed-exploited,
  • verdict (approve / approve-with-notes / block),
  • open questions or scope the review could not cover (e.g., "confirming actual cross-site exploitation requires a live CSRF proof-of-concept, not static review").
Files (vanguard-frontier-agentic)
  • references
    • cookies-and-html-bindings.md 5.1 KB
      # Cookies and {@html} Bindings Review
      
      Use this reference only when the review scope includes a `cookies.set()` call or an `{@html}` template binding.
      
      ## Cookie policy: `cookies.set()`
      
      SvelteKit's `cookies.set()` ships with secure-by-default options: `httpOnly` and `secure` both default to `true` (with `secure` relaxed to `false` only when the request is over plain HTTP on `localhost`, a development convenience), and `sameSite` defaults to `'lax'`. Since SvelteKit v2, `path` must be passed explicitly to `cookies.set()`/`cookies.delete()`/`cookies.serialize()` — the framework will not silently infer one.
      
      ### Non-negotiable design rules
      
      1. **A call that explicitly sets `httpOnly: false` on a session/identity cookie is always a HIGH finding.** It opts out of the framework's own secure default specifically to make the cookie readable from client-side JavaScript — there is no legitimate reason to do this for a session token.
      2. **A call that explicitly sets `secure: false` outside of a documented local-development context is a HIGH finding** on any cookie that carries session or identity information — it permits the cookie to be sent over plain HTTP.
      3. **A missing `sameSite` is not automatically a finding** (the framework default `'lax'` applies), but an explicit `sameSite: 'none'` on a session cookie without a matching `secure: true` is a HIGH finding — `SameSite=None` requires `Secure` per current browser cookie-handling rules.
      4. **A missing `path` is at least a MEDIUM finding** — SvelteKit requires it explicitly since v2 precisely because omitting it previously led to ambiguous, broader-than-intended cookie scoping.
      5. **Do not flag a `cookies.set()` call that only sets non-sensitive, non-identity state** (e.g., a UI preference like `theme=dark`) with the same severity as a session-cookie finding — check what the cookie actually carries before assigning severity.
      
      ## XSS: `{@html}`
      
      Svelte's `{@html ...}` tag injects the given string as raw HTML into the DOM with no escaping or sanitization — it is the direct analog of setting `innerHTML`. Any user-reachable input reaching this tag unsanitized is a stored or reflected XSS vector.
      
      ### Non-negotiable design rules
      
      1. **Trace the `{@html}` argument to its origin.** Follow it backward through component props, `load()` return data, form submissions, and third-party API responses. If any hop in that chain includes content a user or attacker can influence (a comment body, a profile bio, a query parameter reflected into a response, a webhook payload rendered later), the source counts as user-reachable.
      2. **Require a named sanitizer call on the exact traced expression**, not merely present somewhere in the codebase. `{@html DOMPurify.sanitize(value)}` clears the bar; `{@html value}` with a `DOMPurify` import sitting unused nearby does not.
      3. **Fully origin-controlled content is not a finding** — static marketing copy or developer-authored strings with no code path for a user to influence them can be rendered via `{@html}` safely. State this explicitly in the review output rather than silently skipping it, so the scope decision is visible.
      4. **Do not accept "we sanitize on the way into the database" as sufficient** unless you can show the specific write path that populates this exact field always runs through the sanitizer — a second write path (an admin tool, a migration script, a different API route) that bypasses it reintroduces the vulnerability at the same render site.
      
      ## Minimal safe implementation patterns
      
      ```js
      // src/routes/login/+page.server.js — safe cookie policy
      export const actions = {
      	login: async ({ cookies, request }) => {
      		const data = await request.formData();
      		const user = await db.getUser(data.get('email'), data.get('password'));
      		const token = await db.createSession(user);
      		cookies.set('sessionid', token, {
      			path: '/',
      			httpOnly: true,
      			secure: true,
      			sameSite: 'lax'
      		});
      		return { success: true };
      	}
      };
      ```
      
      ```svelte
      <!-- src/lib/components/CommentBody.svelte — safe: sanitized on the exact traced path -->
      <script>
      	import DOMPurify from 'dompurify';
      	let { comment } = $props();
      </script>
      
      <div class="comment-body">
      	{@html DOMPurify.sanitize(comment.body)}
      </div>
      ```
      
      Anti-patterns (do not approve):
      
      ```js
      // WRONG: httpOnly disabled, cookie readable from any injected/XSS'd script
      cookies.set('sessionid', token, { path: '/', httpOnly: false, secure: true, sameSite: 'lax' });
      ```
      
      ```svelte
      <!-- WRONG: user-submitted comment.body rendered with no sanitizer on this path -->
      <div class="comment-body">
      	{@html comment.body}
      </div>
      ```
      
      ## Verification targets
      
      - Grep for `cookies.set(` across `+page.server.js`, `+layout.server.js`, `+server.js`, and form action code; for each match, read the full options object and confirm `httpOnly`, `secure`, `sameSite`, and `path` are all explicit.
      - Grep `.svelte` files for `{@html` and, for each match, trace the bound expression backward through the component's props and any `load()`/store data that feeds it.
      - Grep for `DOMPurify`, `sanitize-html`, or an equivalent sanitizer import; confirm it is actually called inline on the specific `{@html}` expression under review, not merely imported.
      
    • csrf-and-auth-boundaries.md 5.9 KB
      # CSRF and Auth-Boundary Review
      
      Use this reference only when the review scope includes `svelte.config.js`'s `csrf` block, a form action, or an auth-guard trace across `load()`/`+layout.server.js`/`hooks.server.js`.
      
      ## CSRF: `checkOrigin` and `trustedOrigins`
      
      SvelteKit blocks cross-site form submissions by default. At request time, it rejects a form-content-type `POST`/`PUT`/`PATCH`/`DELETE` request whenever the request's origin does not match the app's own origin and is not present in a trusted-origins allowlist. Two config knobs control this:
      
      - `kit.csrf.checkOrigin` — `true` by default. Setting it to `false` disables the origin check entirely.
      - `kit.csrf.trustedOrigins` — an allowlist of additional origins permitted to submit forms cross-site. Setting it to `['*']` disables the check just as thoroughly as `checkOrigin: false`, regardless of what `checkOrigin` is set to — SvelteKit's own config resolution only enables the check when `checkOrigin` is true **and** `trustedOrigins` does not contain `'*'`.
      
      ### Non-negotiable design rules
      
      1. **Treat `checkOrigin: false` and a `'*'` entry in `trustedOrigins` as the same severity of finding.** Both fully defeat the CSRF guard; a reviewer who only greps for `checkOrigin` will miss the wildcard-origin bypass.
      2. **A named `trustedOrigins` list is the correct pattern for legitimate cross-site integrations** (e.g., a trusted partner site embedding a form that posts to this app) — flag only the wildcard, not the presence of the option itself.
      3. **The check only applies to form-content-type requests with a body-bearing method.** JSON API requests handled by `+server.js` endpoints using `fetch` with a custom header are a different trust boundary (typically same-origin `fetch` plus `SameSite` cookie behavior) — do not conflate the two when scoping a finding.
      
      ## Auth boundary: where does the guard actually run?
      
      SvelteKit's own documentation is explicit that `load()`-based auth guards have sharp edges:
      
      - Layout `load()` functions do **not** necessarily re-run on every request — in particular, client-side navigation between child routes can skip re-running a parent layout's `load()`.
      - Layout and page `load()` functions run **concurrently** by default. A child `load()` does not automatically see or wait for the parent's result unless it explicitly calls `await parent()`.
      - The two supported ways to guarantee an auth check runs before protected code: (a) enforce it in `hooks.server.js`'s `handle` function, which runs before any `load()` for every matching request, or (b) put the guard directly in the specific `+page.server.js`/`+server.js` that needs it.
      
      ### Non-negotiable design rules
      
      1. **Do not accept "the layout checks auth" as proof for a specific child page.** Confirm the child's own `load()` calls `await parent()` and inspects the result (e.g., redirects or throws if `parent()`'s data shows no user), or confirm `hooks.server.js` enforces the guard independently of any `load()`.
      2. **`hooks.server.js`'s `handle` is the strongest guarantee** because it runs before `resolve(event)`, which is what triggers `load()` execution — a guard here covers every route it matches regardless of individual `load()` implementations.
      3. **A raw `cookies.get(...)` value is not an identity.** Whatever helper resolves "who is this user" must perform a verifying lookup (session store, signed/encrypted cookie, database check) — passing the cookie's raw string value straight into a data query or a trust decision is an unguarded read keyed on attacker-controlled input, not authentication.
      4. **Form actions need their own check.** A `load()` guard on the page does not protect the co-located `export const actions` handlers — SvelteKit invokes an action directly on form submission; verify each action independently.
      
      ## Minimal safe implementation pattern
      
      ```js
      // svelte.config.js — safe: default checkOrigin, named trusted origins only if needed
      const config = {
      	kit: {
      		// csrf.checkOrigin defaults to true; omit unless you have a documented reason to touch it.
      		csrf: {
      			trustedOrigins: ['https://trusted-partner.example.com']
      		}
      	}
      };
      ```
      
      ```js
      // src/hooks.server.js — safe: guard enforced before any load() runs
      export async function handle({ event, resolve }) {
      	event.locals.user = await getUserFromSession(event.cookies.get('sessionid'));
      	return resolve(event);
      }
      ```
      
      ```js
      // src/routes/account/+page.server.js — safe: guard re-checked at the exact protected path
      import { requireLogin } from '$lib/server/auth';
      
      export async function load(event) {
      	const user = requireLogin(event); // throws/redirects if event.locals.user is absent
      	return { message: `hello ${user.name}!` };
      }
      ```
      
      Anti-pattern (guard only in the parent layout, never confirmed by the child):
      
      ```js
      // src/routes/(protected)/+layout.server.js
      export async function load({ locals }) {
      	if (!locals.user) throw redirect(303, '/login');
      	return { user: locals.user };
      }
      ```
      
      ```js
      // src/routes/(protected)/account/+page.server.js — WRONG: never calls parent(),
      // so this page's own load() has no guard of its own, and if this layout load
      // is skipped on a given navigation, nothing here catches it.
      import * as db from '$lib/server/db';
      
      export async function load({ cookies }) {
      	return { account: await db.getAccount(cookies.get('sessionid')) };
      }
      ```
      
      ## Verification targets
      
      - Grep `svelte.config.js` for `checkOrigin` and `trustedOrigins`; read the exact boolean/array value.
      - Grep every `+page.server.js`/`+layout.server.js`/`+server.js` for `cookies.get(` and follow each result to its next use.
      - Grep for `requireLogin`, `locals.user`, or an equivalent verifying-helper name to confirm a guard call actually exists on the traced path — its absence, combined with a `cookies.get(` feeding a data call, is the clearest structural signal of this defect class.
      - Grep child `load()` functions in a protected route tree for `parent()` to confirm they actually consume the parent's guard result rather than relying on it implicitly.
      
    • workflow-and-output.md 6.9 KB
      # Review Workflow and Findings Contract
      
      Use this reference for the step-by-step review procedure and the required output shape. Load the other two references only for the specific defect class the code under review actually raises.
      
      ## Prerequisites
      
      - Read `package.json` and `svelte.config.js` to confirm the SvelteKit major version, adapter, and the current `kit.csrf` configuration (`checkOrigin`, `trustedOrigins`).
      - Locate every `+page.server.js`, `+layout.server.js`, `+page.js`, `+layout.js`, `+server.js`, and `hooks.server.js` in scope, plus any `.svelte` templates with `{@html}`.
      
      ## Workflow
      
      1. **Check the CSRF configuration.** Read `svelte.config.js`'s `kit.csrf` block. `checkOrigin: false` or `trustedOrigins` containing `'*'` both fully disable the origin check for form-content-type `POST`/`PUT`/`PATCH`/`DELETE` requests. Absence of the block, or `checkOrigin: true` with a named `trustedOrigins` list, is safe. See `references/csrf-and-auth-boundaries.md`.
      2. **Trace every form action.** For each entry under `export const actions`, confirm any sensitive read/write is preceded by an auth check on that exact code path (not assumed from elsewhere in the route tree).
      3. **Trace every `load()` function's auth boundary.** For each `load()`, determine: does this exact function call a verifying helper (`requireLogin()`-style, reading `event.locals` populated by `hooks.server.js`, or an explicit session-store lookup) before fetching/returning sensitive data? If the auth check lives only in a parent `+layout.server.js`, confirm the child explicitly calls `await parent()` and checks the result, or that `hooks.server.js`'s `handle` enforces the guard before any `load()` runs at all. See `references/csrf-and-auth-boundaries.md`.
      4. **Trace every raw cookie value used as an identity claim.** Grep for `cookies.get(...)` and follow its result. If it flows directly into a database call, a trust decision, or a response body with no verification step, that is unguarded — not authenticated.
      5. **Enumerate every `cookies.set()` call.** For each, check that `httpOnly`, `secure`, `sameSite`, and `path` are all explicit and set to secure values. See `references/cookies-and-html-bindings.md`.
      6. **Enumerate every `{@html}` binding.** For each, trace its data source backward through props, `load()` return data, form data, and API responses to the origin. Determine whether the origin includes user-reachable input and whether a named sanitizer call sits on that exact path. See `references/cookies-and-html-bindings.md`.
      7. **Produce ranked findings** using the output contract below.
      
      ## Decision tree
      
      - `svelte.config.js` sets `checkOrigin: false`, or `trustedOrigins` contains `'*'` → **HIGH** finding, `csrf-bypass`. Cite SvelteKit's documented `csrf_check_origin` resolution directly.
      - `csrf.checkOrigin` is left at its default (`true`) and `trustedOrigins` (if present) lists specific origins with no wildcard → not a finding.
      - A `load()` or action fetches/returns sensitive data with no auth check on that exact function's code path → **HIGH** finding, `auth-leakage`.
      - An auth check exists only in a parent `+layout.server.js` `load()`, and the child `+page.server.js`/`+page.js` does not call `await parent()` (or does but never inspects the result), and `hooks.server.js` does not independently enforce the guard → **HIGH** finding, `auth-boundary`. Cite SvelteKit's documented "Implications for authentication" note: layout loads do not always re-run on client-side navigation, and sibling loads run concurrently unless `parent()` is awaited.
      - The guard is enforced in `hooks.server.js`'s `handle` function before `resolve(event)` runs any `load()` → not a finding regardless of what individual `load()` functions do downstream, provided the `handle` guard actually covers the route in scope.
      - A raw `cookies.get(...)` value is passed directly into a database call, an authorization decision, or returned to the client with no verifying lookup → **HIGH** finding, `auth-leakage`.
      - `cookies.set()` omits `httpOnly` or sets it to `false` on a session/identity cookie → **HIGH** finding, `cookie-policy`. `secure: false` on a non-`localhost` origin, or a missing `sameSite` → **HIGH**-to-**MEDIUM** depending on the cookie's sensitivity. A missing `path` → at least **MEDIUM** (SvelteKit requires an explicit `path` since v2 to avoid ambiguous scoping).
      - `cookies.set()` has explicit `httpOnly: true`, `secure: true` (or the documented `localhost`-only relaxation), `sameSite`, and `path` → not a finding.
      - `{@html}` binding's traced data source includes user-reachable input and no sanitizer call is present on that exact path → **HIGH** finding, `xss`. Do not accept "sanitized elsewhere" as clearing this.
      - `{@html}` binding's traced data source is fully origin-controlled (static marketing copy, developer-authored content with no user-submission path) → not a finding, but state this explicitly rather than silently omitting it.
      
      ## Output contract
      
      Every response from this skill must return:
      
      1. **Scope** — the form action(s), `load()` function(s), `hooks.server.js` handle, `svelte.config.js` csrf block, cookie operations, and/or `{@html}` bindings reviewed.
      2. **Ranked findings** — each with file:line, defect category (`csrf-bypass` / `auth-leakage` / `auth-boundary` / `cookie-policy` / `xss`), the concrete data-flow trace naming every hop, and a fix sketch matching SvelteKit's documented pattern.
      3. **Sanitizer/guard status per finding** — for `{@html}` findings, an explicit statement of whether a sanitizer call is present on the traced path; for auth-boundary findings, an explicit statement of whether the guard is enforced in `hooks.server.js` or only in a specific `load()`.
      4. **Evidence level per finding** — `repo evidence`, `documentation-based`, or `inference`. Label structural risk findings as structural risk explicitly — do not imply confirmed exploitation without live evidence.
      5. **Verdict** — approve / approve-with-notes / block.
      6. **Open questions or out-of-scope items** — e.g., "confirming actual cross-site exploitation requires a live CSRF proof-of-concept, not static review," or "client-side reactivity/state-management review is out of scope for this actions-and-load-focused skill."
      
      ## When to push back
      
      Push back if the user asks to:
      
      - approve `trustedOrigins: ['*']` because "we'll tighten it before launch" — a wildcard origin is equivalent to disabling the check entirely, today,
      - treat a `+layout.server.js` auth check as sufficient without confirming the specific child page calls `await parent()` and checks its result, or that `hooks.server.js` independently enforces the guard,
      - approve a raw `cookies.get(...)` value as an identity claim because "it's a random-looking string" — randomness is not verification without a server-side lookup or signature check,
      - downgrade an untraced `{@html}` finding to informational because "it's probably fine" — this skill's default is HIGH for exactly this class of unproven claim.
      
  • metadata.json 2.2 KB
    {
      "id": "sveltekit-actions-load-security-review",
      "name": "SvelteKit Actions & Load Security Review",
      "type": "skill",
      "provider": "frontend",
      "harnesses": [
        "claude-code",
        "cursor",
        "codex",
        "gemini",
        "kiro",
        "other"
      ],
      "summary": "Reviews SvelteKit form actions, load functions, and cookie/HTML bindings for CSRF origin-check bypass, unauthenticated data exposure through load(), auth guards that live only in +layout.server.js without a parent() call in child pages, insecure cookies.set() options, and unsanitized {@html} bindings, grounding claims via Context7 and SvelteKit's own CSRF, cookies, load, and authentication documentation.",
      "source_type": "original",
      "official_docs": [
        "https://svelte.dev/docs/kit/configuration#csrf",
        "https://svelte.dev/docs/kit/load#Cookies",
        "https://svelte.dev/docs/kit/load#Implications-for-authentication",
        "https://svelte.dev/docs/kit/@sveltejs-kit#Cookies",
        "https://svelte.dev/docs/svelte/@html"
      ],
      "security_notes": "This skill's entire scope is security-critical: a disabled/bypassed CSRF check enables cross-site form submission, an unguarded load() or action leaking data via a raw cookie value is an authentication-bypass/data-exposure defect, an auth guard living only in +layout.server.js is a structural auth-boundary gap when a child page's load skips await parent() or hooks.server.js does not enforce the check first, an insecure cookies.set() call (missing/false httpOnly, secure, or sameSite, or a missing path) is a session-hijacking-adjacent cookie-policy defect, and unsanitized {@html} on user-reachable input is a stored/reflected XSS vector. Every finding in this skill defaults to HIGH severity unless proven otherwise with concrete guard/sanitizer evidence on the exact traced path. Static-review-only skill: it reads and greps svelte.config.js, +page.server.js/+layout.server.js/+server.js, form action code, and .svelte templates but never executes, builds, or runs application code, and never sends live requests.",
      "last_verified": "2026-07-03",
      "path": "skills/frontend/sveltekit-actions-load-security-review",
      "author": "github: VincentChuWaiChow",
      "version": "0.1.0"
    }
    
  • SKILL.md 9.4 KB
    ---
    name: sveltekit-actions-load-security-review
    description: Statically review SvelteKit form actions, load functions, hooks, and templates for CSRF origin-check bypass (checkOrigin/trustedOrigins), unauthenticated sensitive-data returns from load(), auth guards confined to +layout.server.js without an enforced parent()/hooks check, insecure cookies.set() options, and unsanitized {@html} bindings, grounded in SvelteKit's own CSRF, cookies, load, and authentication documentation.
    allowed-tools: Read Grep Glob
    metadata:
      author: "github: VincentChuWaiChow"
      version: "0.1.0"
      updated: "2026-07-03"
      category: security
    ---
    
    # SvelteKit Actions & Load Security Review
    
    ## Purpose
    
    Review SvelteKit form actions (`export const actions`), `load()` functions (`+page.server.js`/`+page.js`, `+layout.server.js`/`+layout.js`), `+server.js` endpoints, and template bindings for the concrete, documented defect classes that recur in SvelteKit apps: CSRF protection disabled or weakened via `checkOrigin`/`trustedOrigins`, `load()` returning sensitive data with no auth check on that exact path, an auth guard that lives only in a parent `+layout.server.js` and is silently skipped by a child page that never calls `await parent()` (or by client-side navigation that does not re-run the layout load), cookies set without `httpOnly`/`secure`/`sameSite`/`path` explicitly locked down, and unsanitized `{@html}` rendering of user-reachable input. This skill exists so the review stays anchored to these five documented, security-critical sinks instead of drifting into a general "SvelteKit code review" of routing, reactivity, or component design.
    
    ## When to use
    
    Use this skill when the user asks to:
    
    - review a SvelteKit form action (`export const actions`) or a `load()` function for authentication/authorization correctness,
    - assess whether `svelte.config.js`'s `csrf` block (`checkOrigin`, `trustedOrigins`) is safely configured,
    - investigate whether an authenticated route is actually protected on every entry path (direct page load, client-side navigation, and the action itself),
    - assess whether a `cookies.set()` call or an `{@html}` binding is safe,
    - perform a pre-launch security review of a SvelteKit application's server-side data-loading and mutation surface.
    
    Do not use this skill for:
    
    - general SvelteKit routing, reactivity (`$state`/`$derived`), or component-composition review with no security angle — use a SvelteKit architecture-focused skill instead,
    - a purely client-only Svelte component tree with no `+page.server.js`/`+layout.server.js`/`+server.js`/form-action code and no cookie or `{@html}` usage — there is no server-side sink in scope,
    - a bug that requires live traffic reproduction (a captured cross-site request, a live CSRF proof-of-concept, session-replay capture) to confirm exploitation — static analysis proves the structural risk, not that it has already been exploited in production.
    
    ## Context7 Documentation Protocol
    
    - Resolve the library ID with `resolve-library-id` (matched result: `/sveltejs/kit`) before citing any CSRF-mechanism, cookie-default, or `load()`/auth claim.
    - `/sveltejs/kit` is SvelteKit's own repository (runtime source such as `respond.js`/`cookie.js` plus its documentation tree), so both source-level mechanics and prose guidance are queryable through `query-docs`. Use it to confirm exact runtime behavior — e.g., that `csrf_check_origin` only rejects same-origin-mismatched, form-content-type `POST`/`PUT`/`PATCH`/`DELETE` requests, that `trustedOrigins: ['*']` fully disables the origin check regardless of `checkOrigin`, and that `cookies.set()` defaults `httpOnly` and `secure` to `true` (with `secure` relaxed only on plain-HTTP `localhost`) and `sameSite` to `'lax'`.
    - Before flagging a `+layout.server.js` auth guard as insufficient, confirm via `query-docs` (or the `official_docs` URLs in this skill's `metadata.json` if Context7 is unavailable) that layout `load()` functions do not re-run on every child-route navigation and that layout/page `load()` functions run concurrently unless a child explicitly calls `await parent()` — this is the documented mechanism behind the risk, not an assumption.
    - Read `package.json` and `svelte.config.js` first to confirm the SvelteKit major version and adapter in use — cookie defaults (the `path`-required behavior) and CSRF option shape changed between SvelteKit 1 and 2; do not apply v2 requirements to a v1 codebase or vice versa.
    - If Context7 is unavailable, fall back to the `official_docs` URLs in this skill's `metadata.json` and label the claim `documentation-based, unverified against current release`.
    
    ## Lean operating rules
    
    - CSRF-bypass, auth-leakage, and XSS findings default to HIGH severity. This is a security-scoped skill: do not downgrade a `checkOrigin: false`, a wildcard `trustedOrigins`, an unguarded sensitive `load()` return, or an untraced `{@html}` sanitizer gap to MEDIUM just because it has not been observed exploited yet — the risk is in the structure, not in whether someone has already hit it.
    - Trace every finding to a concrete file:line and a concrete data-flow path. A finding that says "this load() might leak data" or "this cookie might be insecure" without showing the specific `cookies.get()` call, the specific missing guard, or the specific `cookies.set()` options object is not a valid finding — it is a guess.
    - For every `export function load()` (or `export const actions`), determine whether an auth check happens *inside that exact function* (e.g., via a `requireLogin()`-style helper reading `event.locals`/`cookies`) before any sensitive data is fetched or returned. Do not accept "the parent layout checks auth" as sufficient unless the specific child `load()` under review actually calls `await parent()` and that call's result is checked, or `hooks.server.js`'s `handle` function enforces the guard before any `load()` runs.
    - Do not approve a raw `cookies.get(...)` value being passed directly into a database call or trusted as an identity claim. A session/user identity must be resolved through a verifying helper (session-store lookup, signature check, `requireLogin()`) — a lookup with no verification step is not authentication, it is an unguarded read keyed on attacker-controlled input.
    - Check every `cookies.set()` call for explicit `httpOnly`, `secure`, `sameSite`, and `path`. Flag any call that sets `httpOnly: false` (or otherwise turns off a secure default) on a session/identity cookie as HIGH, and flag any call missing `path` as at least MEDIUM (SvelteKit requires an explicit `path` since v2 specifically to avoid ambiguous cookie scoping).
    - Do not approve an `{@html}` binding whose data source includes any user-reachable input (route params, query strings, request bodies, form-submitted content, third-party API responses that themselves echo user input) unless a named sanitizer call (e.g., `DOMPurify.sanitize()`) is visibly present on that exact data-flow path in the template expression. A sanitizer import existing elsewhere in the codebase does not clear this bar.
    - Treat `checkOrigin: false` and `trustedOrigins: ['*']` as equally severe: both fully disable SvelteKit's CSRF origin check for cross-site form submissions, even though only one of them touches the literal `checkOrigin` key.
    - Never execute, build, or run application code, and never send live requests, as part of this review; this is a static-review skill (Read/Grep/Glob only).
    - Load only the reference needed for the concern in scope.
    
    ## References
    
    Load these only when needed:
    
    - [Review workflow and findings contract](references/workflow-and-output.md) — use for the step-by-step review procedure, the CSRF/auth/cookie/XSS decision tree, and the required output shape.
    - [CSRF and auth-boundary review](references/csrf-and-auth-boundaries.md) — load only when the review scope includes `svelte.config.js`'s `csrf` block, a form action, or a `load()`/`+layout.server.js`/`hooks.server.js` auth-guard trace.
    - [Cookies and {@html} review](references/cookies-and-html-bindings.md) — load only when the review scope includes a `cookies.set()` call or an `{@html}` template binding.
    
    ## Response minimum
    
    Return, at minimum:
    
    - the form action(s), `load()` function(s), `hooks.server.js` handle, `svelte.config.js` csrf block, cookie operations, and/or `{@html}` bindings in scope,
    - ranked findings with file:line evidence, defect category (`csrf-bypass`, `auth-leakage`, `auth-boundary`, `cookie-policy`, or `xss`), the concrete data-flow trace (the exact `checkOrigin`/`trustedOrigins` value, the `cookies.get()`-to-sink path, the layout-to-child guard gap, the `cookies.set()` options object, or the `{@html}` origin-to-sink path), and a fix sketch matching SvelteKit's documented pattern,
    - for every `{@html}` finding, an explicit statement of whether a sanitizer call is present on the traced path — never approve on the assumption one exists elsewhere,
    - for every auth-boundary finding, an explicit statement of whether the guard is enforced in `hooks.server.js` (applies to every request) or only in a `+layout.server.js`/`+page.server.js` `load()` (applies only if that exact function runs and, for a layout, only if a child calls `await parent()`),
    - evidence level per finding (`repo evidence`, `documentation-based`, or `inference`), with structural risk findings explicitly labeled as structural risk, not as confirmed-exploited,
    - verdict (approve / approve-with-notes / block),
    - open questions or scope the review could not cover (e.g., "confirming actual cross-site exploitation requires a live CSRF proof-of-concept, not static review").
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related