Claude Skill

sota-web-frameworks

State-of-the-art engineering rules (2026) for the JavaScript SSR meta-frameworks: React 19 + Next.js (App Router, React Server Components, Server Actions) and Vue 3 + Nuxt 4 (Nitro server routes, composables) — plus the cross-cutting concerns of server rendering: hydration correc

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

Full trust report

Download martinholovsky-SOTA-skills-skills_sota-web-frameworks-ec2abf6.zip · 33 KB
Part of martinholovsky/sota-skills — 39 skills

Install

skills CLI npx skills add https://github.com/martinholovsky/SOTA-skills/tree/main/skills/sota-web-frameworks
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install martinholovsky-sota-skills@llmmart
Git git clone https://github.com/martinholovsky/SOTA-skills.git

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

Skill manifest

SOTA Web Frameworks (2026)

Purpose

This skill encodes the 2026 state of the art for the two dominant JavaScript SSR stacks — React + Next.js and Vue + Nuxt — and the server-rendering concerns they share. It is deliberately framework-specific: the traps that matter here (the RSC server/client boundary, Server Actions as public endpoints, hydration mismatches, SSR state leaking across requests, runtimeConfig/NEXT_PUBLIC_ secret boundaries) do not exist at the plain-language level.

Two modes:

  • BUILD — writing or modifying components, routes, and data-fetching to this standard.
  • AUDIT — reviewing an existing app and reporting findings.

Read SKILL.md fully; load rules/*.md on demand per the index below. Verify every version/CVE claim at use time — this file's facts were primary-sourced 2026-07 but framework security moves weekly (see the 2025-12 React Server Components RCE).

Scope boundaries (what lives elsewhere)

This skill stacks on top of the general skills — load them together, don't duplicate:

  • sota-javascript-typescript — strict tsconfig, type design, promises, Node hardening, npm supply chain. The language; this skill is the framework.
  • sota-frontend-design — visual design, layout, components-as-UX, WCAG 2.2 accessibility, motion. This skill covers component engineering, not design.
  • sota-code-security — generic XSS/CSRF/SSRF/authn/authz/crypto theory. This skill covers the framework-specific expression of those (RSC data exposure, Server Action authz, v-html, next/image SSRF).
  • sota-performance rules/06 — Core Web Vitals, bundle budgets, image/font loading. This skill covers render-strategy choice (SSR/SSG/ISR/PPR) and hydration.
  • sota-api-design — REST/GraphQL contract design for the routes themselves.

BUILD mode

  1. Establish context first. Read package.json for the exact React/Next or Vue/Nuxt majors and the render mode in use (App vs Pages Router; Nuxt SSR vs ssr: false). Match the project's baseline — no Server Actions on a Pages-Router app, no defineModel below Vue 3.4. Confirm the versions are supported and patched against the CVE tables in rules/03/rules/05. (rules/01)
  2. Default to the current idiom: React function components + hooks (let the React Compiler memoize — don't hand-write useMemo everywhere); Vue Composition API with <script setup>. Server Components by default in Next App Router, "use client" only at the leaves that need interactivity. (rules/02, rules/04)
  3. The server/client boundary is a security boundary, not just a perf one. Every prop crossing server→client is serialized into the HTML/RSC payload and is public. Authorization lives next to the data (a Data Access Layer / validated server route), never only in middleware or a layout. (rules/03, rules/07)
  4. Treat every Server Action and Nitro route as a public, unauthenticated HTTP endpoint until it validates input and checks authz itself — even if it looks internal or is never imported. (rules/03, rules/05, rules/07)
  5. Get hydration right by construction: deterministic render (no Date.now(), Math.random(), or window in render), stable IDs via useId, SSR-safe shared state (useState/per-request instances, never module-level refs). (rules/06)
  6. Serialize SSR state safely (framework serializer or escaped JSON, never naked JSON.stringify into <script>) and wire CSP (nonce or hash) knowing it forces dynamic rendering in Next. (rules/06, rules/07)
  7. Tests accompany code (sota-testing): component tests plus at least one test that exercises the server/client boundary or a server route's authz.

AUDIT mode

  1. Fingerprint + patch-check first. Pin exact framework/loader versions from the lockfile and diff them against the CVE tables in rules/03 and rules/05. An unpatched React2Shell (CVE-2025-55182) or middleware bypass (CVE-2025-29927) is CRITICAL on its own, before any code is read.
  2. Trace the trust boundary. Grep "use client"/"use server", defineProps, runtimeConfig, NEXT_PUBLIC_/NUXT_PUBLIC_; find where server data crosses to the client and where authz is enforced. Middleware/layout-only authz is a finding.
  3. Run each relevant rules file's Audit checklist (grep-driven), then read for design: hydration determinism, SSR state isolation, caching of personalized pages.
  4. Verify every finding — a v-html fed a constant is not XSS; a Server Action that re-checks the session is not IDOR. Note mitigations already present.

Severity conventions

Severity Meaning Examples
CRITICAL Exploitable now / RCE / data loss Unpatched RSC deserialization RCE; middleware-bypass auth on the only authz layer; secret in NEXT_PUBLIC_/public runtimeConfig; unserialize-class sink
HIGH Exploitable with preconditions Server Action / Nitro route with no authz (IDOR); SSRF via next/image ** or user-URL fetch; dangerouslySetInnerHTML/v-html of user data; whole-DB-row as a client prop
MEDIUM Real but bounded, or reliability Personalized SSR page cacheable at a shared CDN; hydration mismatch on user data; missing CSP; module-level SSR state; unpatched non-critical CVE
LOW Deviation from SOTA Pages Router for greenfield; hand-rolled memoization vs React Compiler; $fetch double-fetch in setup; Options API for a new app
INFO Worth knowing Newer render mode available; migration opportunities

Finding format

file:line | rule violated (rules/NN §S) | severity | effort | fix

Effort: trivial · small · medium · large. Group by severity, CRITICAL first. Borderline severities state the deciding assumption; unconfirmed findings are marked "needs verification", never asserted. End with per-severity counts, the sweep commands run, and explicit "checked and clean" areas.

Rules index

File Read this when...
rules/01-baseline.md choosing or verifying a stack and version floor (React/Next/Vue/Nuxt support+EOL matrix), picking a render mode (CSR/SSR/SSG/ISR/PPR), project setup, React Compiler
rules/02-react.md writing React components: hooks rules, useId/useEffect/refs, Suspense & error boundaries, use()/Actions/useActionState, memoization & the React Compiler, dangerouslySetInnerHTML
rules/03-nextjs.md Next.js App Router: Server vs Client Components, Server Actions, the caching model (use cache/Cache Components/PPR/ISR/revalidate), proxy.ts/middleware, the Data Access Layer, and Next CVEs
rules/04-vue.md writing Vue: Composition API & <script setup>, reactivity pitfalls (props destructure, shallowRef, watchers, effectScope), defineModel, TypeScript, and Vue XSS (v-html, template injection)
rules/05-nuxt.md Nuxt 4: data fetching (useFetch/useAsyncData/$fetch), useState, runtimeConfig, Nitro server routes & auth, routeRules/hybrid rendering, islands, and Nuxt/Nitro/h3/IPX CVEs
rules/06-ssr-hydration.md anything SSR: hydration mismatches & determinism, SSR state-serialization XSS, cross-request state pollution, caching personalized pages safely, CSP with streaming SSR
rules/07-security.md a security pass: the server/client secret boundary, authorization placement (post-CVE-2025-29927), SSRF surfaces, the consolidated framework CVE reference, and supply-chain notes

Top-10 non-negotiables

  1. Run supported, patched majors. React ≥ 19.x, Next ≥ 15.x (16.x current), Vue ≥ 3.5, Nuxt ≥ 4.x (Nuxt 3 security-only until 2026-07-31). Cross-check the CVE tables — an unpatched RSC RCE (CVE-2025-55182) or middleware bypass (CVE-2025-29927) is a ship-stopper. (rules/01, rules/03, rules/05)
  2. Authorization lives next to the data, never only in middleware or a layout. proxy.ts/middleware is an optimization, not the auth layer; each Server Action, Route Handler, and Nitro route re-checks authn + authz. (rules/03, rules/07)
  3. Every prop that crosses server→client is public. It's serialized into the RSC payload / HTML. Pass minimal DTOs, never raw DB rows, tokens, or process.env. (rules/03, rules/07)
  4. Server Components by default; "use client" only where interactivity requires it. Keep secrets and data access in Server Components / server-only modules (import 'server-only'). (rules/02, rules/03)
  5. Secrets never reach the client bundle. Nothing sensitive in NEXT_PUBLIC_*, VITE_*, or runtimeConfig.public — those are inlined at build time. (rules/07)
  6. Validate every server-route/action input with a schema (getValidatedBody/Zod; never trust formData, searchParams, or headers), and check resource ownership to prevent IDOR. (rules/03, rules/05)
  7. Hydrate deterministically: no Date/random/window in render, stable IDs via useId, SSR-safe shared state — and never "fix" a mismatch by injecting server-side user HTML. (rules/06)
  8. Serialize SSR state safely. Framework serializer (devalue) or <-escaped JSON in <script>; naked JSON.stringify into a script tag is XSS. (rules/06)
  9. User HTML is dangerous HTML. dangerouslySetInnerHTML/v-html only on sanitizer output; validate href/URL schemes (javascript:); lock next/image remotePatterns to explicit hosts (no **). (rules/02, rules/04, rules/07)
  10. Don't cache personalized SSR at a shared cache. Cache-Control: private for per-user pages; know that CDNs drop Vary; wire CSP nonces knowing they force dynamic rendering. (rules/06)
Files (sota-skills)
  • rules
    • 01-baseline.md 6.8 KB
      # 01 — Baseline: versions, support windows, render modes
      
      Fast-moving facts. Every version and EOL date below was primary-sourced 2026-07;
      **re-verify at use time** before pinning — these stacks ship majors yearly and
      security releases weekly.
      
      ## 1. Supported versions and EOL (verify before pinning)
      
      | Runtime | Current stable (2026-07) | Floor for new code | EOL / support note |
      |---|---|---|---|
      | React | 19.2.x | 19.x | 18.x maintained but no new features; React Compiler needs 19 idioms |
      | Next.js | 16.2.x | 15.x (16.x preferred) | **Active LTS 16.x**, **Maintenance LTS 15.x** (critical + security ~2y from 2024-10-21). Everything **< 15 is unsupported** — treat as a finding |
      | Vue | 3.5.x | 3.5+ | Vue 2 **EOL 2023-12-31** (paid extended support only). 3.6 (Vapor mode) in beta — not stable |
      | Nuxt | 4.4.x | 4.x | Nuxt 3 **security-patches-only until 2026-07-31** (then EOL); Nuxt 2 EOL 2024-06-30; Nuxt 5 (unreleased) brings Nitro v3 + h3 v2 |
      | Nitro | 2.13.x | 2.x | v3 in beta; ships with Nuxt 5 |
      | Pinia | 3.x | 3.x | Pinia 3 dropped Vue 2; default Nuxt/Vue store |
      
      Sources: react.dev/versions, nextjs.org/support-policy, github.com/vuejs/core
      releases, nuxt.com/docs/4.x/community/roadmap. Running an EOL major (Vue 2, Nuxt 2,
      Next < 15) means no security patches — HIGH at minimum for an internet-facing app.
      
      - **React Compiler 1.0 is stable** (2025-10-07): a build-time plugin that
        auto-memoizes, removing most manual `useMemo`/`useCallback`/`memo`. It requires
        Rules-of-Hooks-clean code (see `rules/02`). Opt-in for Next/Vite today; verify
        current adoption status for your toolchain. Prefer it over hand-memoization for
        new code, but it is not a substitute for fixing render-model mistakes.
      
      ## 2. Choosing a stack (framework selection)
      
      Both stacks are mature and SSR-first; the choice is usually ecosystem/team, not
      capability. Neutral guidance, not prescription:
      
      - **React + Next.js** — largest ecosystem; React Server Components + Server Actions
        are the reference implementation of the RSC model; the deepest hosting integration
        (Vercel and others). Cost: the RSC/client mental model is genuinely hard, and the
        security surface is large and fast-moving (see the 2025-12 RSC RCE).
      - **Vue + Nuxt** — gentler learning curve, batteries-included conventions (file-based
        routing, auto-imports, `runtimeConfig`), Nitro gives a portable server runtime.
        Smaller but healthy ecosystem.
      - **Not every app needs a meta-framework.** A purely client-side app (internal
        dashboard behind auth, no SEO need) can be a plain Vite SPA — you shed the entire
        SSR/hydration/RSC attack surface. Reach for Next/Nuxt when you need SSR/SSG for SEO,
        fast first paint, or server-side data access. Don't adopt SSR for its own sake.
      
      Record the decision as an ADR (`sota-docs-workflow`); it drives everything downstream.
      
      ## 3. Render modes — pick per route, not per app
      
      The single most consequential design choice. Modern frameworks let you mix modes
      per route, so match each route to its data:
      
      | Mode | What it is | Use for | Watch out for |
      |---|---|---|---|
      | **CSR** (client only) | JS renders in the browser; empty initial HTML | Highly interactive, auth-gated, no-SEO views (`ssr: false` route in Nuxt) | Blank first paint; not indexable |
      | **SSR** (per request) | HTML rendered on each request | Personalized/authenticated pages, fresh data | Server cost; **must not be cached at a shared CDN if personalized** (`rules/06`) |
      | **SSG / prerender** | HTML rendered at build | Marketing, docs, anything static | Rebuild to update; no per-user content |
      | **ISR / SWR** | Static + periodic/on-demand revalidation | Semi-static content (catalogs, blogs) | Stale windows; cache-invalidation correctness |
      | **PPR** (Next, Cache Components) | Static shell + streamed dynamic holes via Suspense | Pages mixing static chrome + dynamic data | Uncached data outside `<Suspense>` is a build error; incompatible with CSP nonces |
      
      - **Next.js**: route-segment config + `"use cache"`; PPR is the default when
        `cacheComponents: true`. Fetch is **not cached by default since v15** — opt in
        explicitly (`rules/03`).
      - **Nuxt**: `routeRules` in `nuxt.config` sets per-route mode (`ssr: false`,
        `prerender: true`, `swr: <ttl>`, `isr: <ttl>`) — hybrid rendering (`rules/05`).
      - **Security corollary:** the more a route is cached and shared, the more a caching
        bug leaks one user's data to another. Personalized ⇒ SSR + `private` cache. Static
        ⇒ safe to cache widely. Decide this consciously per route.
      
      ## 4. Project setup baseline
      
      - **TypeScript strict** — non-negotiable; details in `sota-javascript-typescript`
        rules/01. Frameworks generate a `tsconfig`; extend, don't loosen it.
      - **Lockfile committed**, exact framework versions pinned, Dependabot/Renovate on —
        framework CVEs are frequent and the fix is almost always "upgrade" (`rules/07`,
        `sota-devsecops`).
      - **Lint the framework rules**: `eslint-plugin-react-hooks` (Rules of Hooks — also
        what the React Compiler needs) / `eslint-plugin-vue`; Next's and Nuxt's own ESLint
        configs. A hooks-rule violation is a real bug, not style.
      - **Env discipline from day one**: server secrets in unprefixed env vars; only
        deliberately-public config in `NEXT_PUBLIC_*` / `runtimeConfig.public` / `VITE_*`
        (`rules/07`). Never commit `.env`.
      - **CI gates**: typecheck, lint, tests, `npm audit`/`osv-scanner`, and a build. Add a
        bundle-size check if shipping to the browser (`sota-performance` rules/06).
      
      ## Audit checklist
      
      ```bash
      # Framework majors in use — compare against the support table above
      grep -E '"(react|react-dom|next|vue|nuxt|nitropack|pinia)"' package.json
      cat package.json | grep -A2 '"dependencies"'   # then read lockfile for exact patch
      
      # EOL / unsupported runtimes (findings)
      node -e "const p=require('./package.json');const d={...p.dependencies,...p.devDependencies};for(const k of ['vue','nuxt','next'])if(d[k])console.log(k,d[k])"
      # vue ^2 -> EOL; nuxt ^2 -> EOL; next <15 -> unsupported
      
      # Render-mode inventory
      grep -rn 'ssr:\s*false\|routeRules\|prerender\|export const dynamic\|cacheComponents' nuxt.config.* next.config.* app/ pages/ 2>/dev/null
      
      # Hooks/vue lint present?
      grep -rn 'react-hooks\|eslint-plugin-vue\|next/core-web-vitals\|@nuxt/eslint' .eslintrc* eslint.config.* package.json 2>/dev/null
      ```
      
      - [ ] Every framework major is supported and receiving security patches (no Vue 2, Nuxt 2, Next < 15)?
      - [ ] Exact versions pinned + lockfile committed + automated dependency updates on?
      - [ ] Render mode chosen per route to match its data (personalized ⇒ SSR + private cache)?
      - [ ] TypeScript strict; hooks/vue lint rules enforced in CI?
      - [ ] Public-env boundary understood: no secrets in `NEXT_PUBLIC_`/`public`/`VITE_`?
      - [ ] For a no-SEO auth-gated app: is a meta-framework actually needed, or would a plain SPA shed the SSR attack surface?
      
    • 02-react.md 7.5 KB
      # 02 — React 19: components, hooks, Actions, memoization
      
      Scope: React itself (any renderer). Next-specific RSC/Server-Action mechanics are in
      `rules/03`; hydration in `rules/06`. TypeScript-with-React lives in
      `sota-javascript-typescript`.
      
      ## 1. Component and hook fundamentals
      
      - **Function components + hooks only.** Class components are legacy; no new ones.
      - **Rules of Hooks are load-bearing, not style.** Call hooks unconditionally, at the
        top level, in the same order every render. Violations are real bugs *and* they
        break the React Compiler (which refuses to optimize non-conforming components).
        Enforce with `eslint-plugin-react-hooks`.
      - **`useEffect` is for synchronizing with external systems**, not for deriving data.
        The react.dev guide "You Might Not Need an Effect" is the reference: compute derived
        values during render, adjust state during render (not in an effect), fetch via a
        framework loader or a data library — not a bare `useEffect(fetch)`, which races and
        double-runs. An effect that only sets state from props/state is a smell.
      - **Every effect that subscribes must clean up** (return a teardown): listeners,
        timers, sockets, subscriptions. A missing cleanup leaks across Strict Mode's
        double-invoke and across remounts.
      - **Stable IDs come from `useId`**, never `Math.random()` or a module counter — this
        is what keeps SSR and client markup matching (`rules/06`). For form-field `id`/
        `htmlFor` pairs and ARIA wiring.
      - **Refs (`useRef`) are escape hatches**: DOM access and mutable non-render values.
        Don't read/write `ref.current` during render. Prefer state for anything that should
        trigger a re-render.
      
      ## 2. Suspense, transitions, and error boundaries
      
      - **Suspense** declares loading UI for async children (lazy components, RSC/data that
        suspends). Pair every dynamic boundary with a fallback; in Next PPR, uncached data
        *must* sit inside a `<Suspense>` or the build fails (`rules/03`).
      - **`useTransition` / `startTransition`** mark state updates as non-urgent so input
        stays responsive; `useDeferredValue` defers re-rendering expensive subtrees. Reach
        for these on measured jank, not preemptively.
      - **Error boundaries are mandatory around risky subtrees** (lazy loads, third-party
        widgets, RSC islands). Hooks can't catch render errors — you still need a class
        boundary or `react-error-boundary`. Server-render errors and hydration errors
        surface here too. Don't render user-controlled error text as HTML.
      
      ## 3. The Actions model (React 19)
      
      React 19 stabilized "Actions" — async functions wired into transitions with built-in
      pending/error/optimistic state.
      
      - **`useActionState(action, initial)`** — wraps an async action, returns
        `[state, formAction, isPending]`; drive a `<form action={formAction}>`. Replaces
        hand-rolled `isLoading`/`error` state around form submits.
      - **`useOptimistic`** — show an optimistic value while the action is in flight,
        auto-reverting on failure.
      - **`use(promise)` / `use(context)`** — unwrap a promise (suspends) or read context,
        and unlike other hooks `use` may be called conditionally. Feed it a *cached/stable*
        promise (from an RSC or a cache), never a promise created inline in render — that
        creates a new promise every render and never resolves.
      - **`ref` is a regular prop** in React 19 (no more `forwardRef` for new code);
        `ref` cleanup callbacks are supported.
      - These integrate with Next Server Actions (`rules/03`): `formAction` can be a
        `"use server"` function. The client-side pending/optimistic UX is React; the
        security of the action is server-side and covered in `rules/03`/`rules/07`.
      
      ## 4. Memoization and the React Compiler
      
      - **Prefer the React Compiler over manual memoization.** Compiler 1.0 (stable,
        2025-10) auto-memoizes components and values, making most `useMemo`, `useCallback`,
        and `React.memo` unnecessary — *if* your code follows the Rules of Hooks and treats
        props/state as immutable. Don't rip out existing memoization blindly, but stop
        hand-writing new memo wrappers as a reflex.
      - **Where you still memoize by hand** (no compiler, or a proven hot path): memoize the
        *expensive* computation or a referentially-stable callback passed to a memoized
        child. Memoizing a cheap value costs more than it saves. Measure (`sota-performance`).
      - **Never mutate state or props.** Compiler correctness and React's rendering both
        assume immutability; mutation causes stale UI and defeats memoization. Use immutable
        updates (spread, `.map`, or a library).
      - **Keys must be stable and unique** across siblings — never the array index for
        reorderable/insertable lists (causes state to attach to the wrong row and subtle
        data-corruption bugs). Use a domain ID.
      
      ## 5. React-specific XSS: the injection sinks
      
      Generic XSS theory is in `sota-code-security` rules/05; here are the React sinks.
      
      - **`dangerouslySetInnerHTML` is the one HTML-injection sink** JSX gives you (JSX
        text is auto-escaped). Only ever feed it sanitizer output (DOMPurify or a server
        sanitizer with an allowlist), never raw user/CMS HTML.
      
      ```jsx
      // BAD — HIGH: stored/reflected XSS
      <div dangerouslySetInnerHTML={{ __html: comment.body }} />
      
      // GOOD — sanitize first (allowlist), or don't use HTML at all
      import DOMPurify from 'dompurify';
      <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(comment.body) }} />
      ```
      
      - **URL/`href` injection:** `<a href={userUrl}>` allows `javascript:` and `data:`
        schemes. Validate the scheme (`https?:`/relative) before rendering; the same applies
        to `src`, `formAction`, and `<img src>`.
      - **Spreading unknown props** (`<div {...userControlledObject}>`) can inject
        `dangerouslySetInnerHTML` or event handlers — never spread attacker-influenced
        objects onto DOM elements.
      - **Rendering into a portal / third-party DOM** you don't control inherits that DOM's
        trust; don't mount React onto server-rendered nodes that contain user HTML
        (mirrors the Vue rule in `rules/04`; hydration-XSS angle in `rules/06`).
      - CSP is defense-in-depth, not a substitute (`rules/06`, `rules/07`).
      
      ## Audit checklist
      
      ```bash
      # HTML injection sink — every hit needs a sanitizer on the source
      grep -rn 'dangerouslySetInnerHTML' --include='*.tsx' --include='*.jsx' src app components
      
      # javascript:/data: URL sinks
      grep -rnE 'href=\{|src=\{|formAction=\{' --include='*.tsx' src app | grep -iv 'sanitiz'
      
      # Effect smells: fetch-in-effect, setState-only effects, missing deps
      grep -rnE 'useEffect\(' --include='*.tsx' src app | head
      grep -rn 'Math.random()\|Date.now()' --include='*.tsx' src app   # in render => hydration bug (rules/06)
      
      # Rules of Hooks / compiler-blocking violations rely on lint:
      grep -rn 'react-hooks' .eslintrc* eslint.config.* package.json
      
      # Legacy patterns
      grep -rn 'forwardRef\|class .* extends .*Component' --include='*.tsx' src app   # forwardRef unneeded in 19
      grep -rnE 'key=\{.*index' --include='*.tsx' src app   # index-as-key on dynamic lists
      ```
      
      - [ ] Every `dangerouslySetInnerHTML` fed sanitizer output (allowlist), not raw user/CMS HTML?
      - [ ] `href`/`src`/URL props validated against a scheme allowlist (no `javascript:`/`data:`)?
      - [ ] No `Math.random()`/`Date.now()`/`window` used *during render* (hydration determinism)?
      - [ ] Effects only for external synchronization, each with cleanup; no fetch-in-bare-effect racing?
      - [ ] Stable domain IDs as list keys (not array index) for insertable/reorderable lists?
      - [ ] Rules of Hooks enforced by lint (also required for the React Compiler)?
      - [ ] Error boundaries around lazy/third-party/RSC subtrees; error text not rendered as HTML?
      
    • 03-nextjs.md 11.8 KB
      # 03 — Next.js: App Router, Server Actions, caching, CVEs
      
      Baseline: Next.js 16.x (App Router). Pages Router still ships but is not the
      recommended model for new code. Hydration is in `rules/06`; the consolidated
      security boundary and CVE reference in `rules/07`. **Re-verify every CVE range at
      use time** — the version numbers below were primary-sourced 2026-07.
      
      ## 1. Server vs Client Components — the boundary is everything
      
      App Router components are **Server Components by default**. They run only on the
      server, can be `async`, and can touch the database, filesystem, and secrets directly.
      
      - **`"use client"` marks the boundary**, not a single component — everything imported
        into a `"use client"` module becomes client code. Push the directive to the leaves
        that actually need interactivity (state, effects, event handlers, browser APIs).
      - **Everything a Server Component passes as a prop to a Client Component is serialized
        into the RSC payload and shipped to the browser** — it is public. The canonical
        anti-pattern is passing a whole DB row (with password hashes, internal flags) when
        the client needs two fields. Pass minimal DTOs.
      - **Keep secrets and data access server-side.** Mark server-only modules with
        `import 'server-only'` so importing them from a Client Component is a *build error*.
        Only server code should read `process.env` secrets.
      - **Most** functions and class instances can't cross the boundary; React throws. That's a
        feature — it stops you leaking server closures to the client. Know the exceptions before
        you report one as a violation: React's serializable list **does** include `Date`, `Map`,
        `Set`, `TypedArray`, `ArrayBuffer`, promises, JSX elements, and **functions that are
        Server Functions** (`'use server'`). What is not serializable is a function *not* exported
        from a client-marked module or marked `'use server'`, a class, an instance of any
        non-built-in class, a null-prototype object, and a non-global symbol
        ([React: serializable types](https://react.dev/reference/rsc/use-client), verified
        2026-09-16). The minimal-DTO advice below stands on its own security merits.
      
      ## 2. Server Actions — public endpoints with ergonomic syntax
      
      A `"use server"` function is compiled into a **public HTTP POST endpoint**. This is
      the highest-value Next security topic.
      
      ```tsx
      'use server';
      export async function deletePost(id: string) {
        const user = await requireUser();           // 1. authenticate — every time
        const post = await db.post.find(id);
        if (post.authorId !== user.id) throw new Error('forbidden'); // 2. authorize (ownership)
        const parsed = z.string().uuid().parse(id); // 3. validate input
        await db.post.delete(parsed);
      }
      ```
      
      - **Every action re-checks authn + authz + input**, even if it's never imported into
        a page and even if it "looks internal." Next's dead-code elimination and encrypted,
        per-build action IDs raise the bar but the docs are explicit: treat every action as
        externally reachable. Missing authz here is IDOR (HIGH).
      - **Validate all input with a schema** (Zod/Valibot). `formData`, arguments, and any
        reflected header are attacker-controlled. Never trust a hidden field like
        `isAdmin`.
      - **Return values are serialized to the client** — filter them the same as a prop
        (don't return the raw row).
      - **CSRF:** Next checks Origin vs Host for actions; behind a proxy set
        `experimental.serverActions.allowedOrigins`. Closure-captured variables are
        encrypted per build (key `NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` — set it explicitly
        for multi-replica deploys so restarts don't invalidate in-flight forms);
        `.bind()` arguments are **not** encrypted. Don't rely on encryption for authz.
      - **Route Handlers** (`route.ts`) are the same story — treat every one as a public
        API endpoint (`sota-api-design`, `sota-code-security`).
      
      ## 3. Authorization placement — the Data Access Layer
      
      Post-CVE-2025-29927 (middleware bypass, below), the official guidance is unambiguous:
      **do not put your only authorization check in middleware or a layout.**
      
      - **`proxy.ts`/middleware** (renamed from `middleware.ts` in Next 16; `proxy.ts`
        defaults to the Node runtime) is for coarse, optimistic checks (redirect if no
        session cookie) — an optimization, not the security boundary. It can be bypassed
        (CVE-2025-29927) and its matcher can silently stop covering a route.
      - **Layouts don't re-render on client navigation**, so a layout-level auth check is
        not re-evaluated as the user navigates — insufficient on its own.
      - **Data Access Layer (DAL):** a `server-only` module that every read/write goes
        through, which authenticates, authorizes, and returns minimal DTOs. Authorization
        lives *next to the data*. This is the pattern the Next docs recommend for new apps.
      - **Taint APIs** (`experimental_taintObjectReference`/`taintUniqueValue`) are still
        experimental (`experimental.taint`) — a backstop that throws if a tainted object
        reaches the client, not a primary control (cloning/deriving escapes the taint).
      
      ## 4. The caching model (know what's cached, and when)
      
      The caching model changed materially; stale mental models cause both bugs and leaks.
      
      - **`fetch` is not cached by default since v15.** Opt in per call
        (`fetch(url, { cache: 'force-cache' })` or `next: { revalidate: N }`) or via route
        config. Don't assume memoized fetches.
      - **Cache Components** (`cacheComponents: true` in `next.config`, formerly the
        experimental `dynamicIO`) turns on the `"use cache"` directive and makes **PPR the
        default**: a static shell plus dynamic holes streamed through `<Suspense>`. Any
        uncached dynamic data *outside* a Suspense boundary is a build error.
      - **`"use cache"`** caches a function/component's output; defaults are ~5 min client
        stale / ~15 min server revalidate; tune with `cacheLife()` and tag with
        `cacheTag()`. Cache keys include the build ID, the function ID, and serialized
        arguments/closed-over values — **so a user-specific value in the closure becomes
        part of the key** (correct) but caching a *component that renders per-user data
        without keying on the user* leaks across users (MEDIUM–HIGH). Variants:
        `use cache: private` for per-user.
      - **Invalidation, and the allowed context differs per API:** `revalidateTag` and
        `revalidatePath` work from a Server Action **or** a Route Handler. **`updateTag` is
        Server-Actions-only** — calling it from a Route Handler *throws*
        (`updateTag can only be called from within a Server Action`), so reach for
        `revalidateTag` there. This is an invocation-context rule, not a caching-style
        preference ([Next.js: updateTag](https://nextjs.org/docs/app/api-reference/functions/updateTag),
        verified 2026-09-16). **ISR** (route `revalidate`) still works.
      - **Security rule:** never cache a personalized page at a shared cache. If a route
        reads the session/cookies, it must be dynamic or explicitly `private`. Cache
        poisoning has been a repeated Next CVE class (below) — CDNs also drop `Vary`, so
        don't rely on it (`rules/06`).
      
      ## 5. Next.js CVE reference (verify ranges at use time)
      
      Unpatched, several of these are CRITICAL on their own. Fingerprint the exact version
      from the lockfile and compare.
      
      | CVE / advisory | Class | Fixed in | Note |
      |---|---|---|---|
      | **CVE-2025-55182** ("React2Shell") | **RSC deserialization RCE, CVSS 10.0** | react-server-dom-* 19.0.1 / 19.1.2 / 19.2.1 | The React-level flaw; exploited in the wild within hours of 2025-12-03 disclosure |
      | **CVE-2025-66478** (GHSA-9qr9-h5gf-34mp) | Next.js surface of React2Shell | 15.0.5/15.1.9/15.2.6/15.3.6/15.4.8/15.5.7/16.0.7 | Next 15.x/16.x/14.3-canary.77+ affected; **rotate secrets if it ran unpatched** |
      | CVE-2025-55184 / -55183 / -67779 / CVE-2026-23864 | RSC DoS + Server-Function source exposure (React2Shell follow-ups) | see 2025-12-11 + later advisories | Upgrade to the latest patch on your line |
      | **CVE-2025-29927** (GHSA-f82v-jwr5-mffw) | **Middleware auth bypass** via `x-middleware-subrequest`, CVSS 9.1 | 12.3.5/13.5.9/14.2.25/15.2.3 | Self-hosted; strip the header at the proxy; don't rely on middleware for authz |
      | CVE-2024-46982 (GHSA-gp8f-8m3g-qvj9) | Cache poisoning (Pages Router) | 13.5.7 / 14.2.10 | + CVE-2025-32421 low-sev bypass (<15.1.6) |
      | CVE-2025-49005 (GHSA-r2fc-ccr8-96c4) | RSC cache poisoning via missing `Vary` | 15.3.3 | App Router |
      | CVE-2024-34351 (GHSA-fr5h-rqp8-mj6g) | SSRF in Server Actions via Host header | 14.1.1 | Self-hosted redirect handling |
      | CVE-2025-57822 (GHSA-4342-x723-ch2f) | Middleware redirect → SSRF | 14.2.32 / 15.4.7 | Unsanitized headers into `NextResponse.next()` |
      | CVE-2024-56332 (GHSA-7m27-7ghc-44w9) | Server Actions DoS | 13.5.8/14.2.21/15.1.2 | |
      | CVE-2026-44581 (GHSA-ffhc-5mcf-pf4q) | XSS in CSP-nonce apps | 15.5.16 / 16.2.5 | Malformed nonce reflected; cache-poisonable |
      | CVE-2025-55173 (GHSA-xv57-4mr9-wg8v) / CVE-2025-57752 | next/image content injection / cross-user image cache confusion | 14.2.31 / 15.4.5 | Precondition: permissive `remotePatterns`/`domains` |
      | GHSA-c4j6-fc7j-m34r + 2026-05 batch | WebSocket-upgrade SSRF; proxy/segment-prefetch bypasses; Cache-Components DoS | latest 15.5.x / 16.2.x | No confirmed CVE on the WS-SSRF advisory; upgrade to current patch |
      
      **next/image SSRF:** `remotePatterns` with a `**` wildcard host turns the
      `/_next/image` optimizer into a blind-SSRF proxy (reachable internal URLs / metadata
      endpoint), and it follows redirects from an allowed host without re-validating — an
      open redirect on an allowlisted domain becomes SSRF. Allowlist explicit hosts,
      protocols, and paths (`rules/07`).
      
      ## Audit checklist
      
      ```bash
      # Exact version — compare to the CVE table
      node -e "console.log(require('./node_modules/next/package.json').version)"
      grep -E '"(react|react-dom|react-server-dom-webpack|next)"' package.json
      
      # Server Actions / Route Handlers — each must authn+authz+validate
      grep -rn "'use server'" --include='*.ts' --include='*.tsx' app lib
      grep -rlnE 'export async function (GET|POST|PUT|DELETE|PATCH)' app  # -E: without it the
                                                                          # parens are LITERAL in BRE
                                                                          # and this silently finds 0
      
      # Authz only in middleware/layout? (finding)
      ls middleware.* proxy.* 2>/dev/null; grep -rn 'getServerSession\|auth()\|requireUser' app | head
      
      # Server->client data exposure: whole objects as props, env on client
      # NO negative lookahead: POSIX ERE has none, so `(?!...)` is a syntax error or a literal.
      # List the env reads, then exclude the public prefix with a second pass.
      grep -rnE 'process\.env\.[A-Z0-9_]+' --include='*.tsx' app components | grep -v 'NEXT_PUBLIC_'
      # NB this is a CANDIDATE list, not an absence proof: it greps text, not the Client Component
      # module graph. A server module imported by a client one is invisible to it.
      grep -rn "import 'server-only'\|import \"server-only\"" app lib   # want: present in data layer
      
      # next/image SSRF precondition
      grep -rn "remotePatterns\|images:\s*{" next.config.* | grep -n '\*\*\|domains'
      
      # Caching of personalized routes
      grep -rn "use cache\|cacheComponents\|force-cache\|revalidate\|Cache-Control" app next.config.*
      ```
      
      - [ ] Exact Next + react-server-dom versions patched against CVE-2025-55182/-66478 and CVE-2025-29927?
      - [ ] Every Server Action and Route Handler authenticates, authorizes (ownership/IDOR), and schema-validates input — not relying on middleware?
      - [ ] No secrets or whole DB rows crossing server→client; data layer marked `server-only`; DTOs minimal?
      - [ ] Authorization enforced at the data layer, not only in `proxy.ts`/middleware or a layout?
      - [ ] Caching understood per route; no personalized page cached at a shared cache; `use cache` keyed per-user where needed?
      - [ ] `next/image` `remotePatterns` limited to explicit trusted hosts (no `**`)?
      - [ ] `NEXT_SERVER_ACTIONS_ENCRYPTION_KEY` set for multi-replica deploys; `allowedOrigins` configured behind a proxy?
      
    • 04-vue.md 6.7 KB
      # 04 — Vue 3: Composition API, reactivity, XSS
      
      Baseline: Vue 3.5.x. Nuxt-specific concerns (data fetching, `useState`, server routes)
      are in `rules/05`; hydration in `rules/06`. TypeScript setup depth is in
      `sota-javascript-typescript`.
      
      ## 1. Composition API + `<script setup>` is the default
      
      - **For applications, use the Composition API with `<script setup>`** — Vue's own
        recommendation. Options API remains fully supported and is fine for progressive
        enhancement / no-build-step sprinkles, but new app code is Composition API.
      - Don't mix paradigms within a component without reason; consistency aids the compiler
        and the reader.
      - **Vue 3.6 (beta, not stable) adds Vapor mode** — an opt-in, per-SFC compilation
        (`<script setup vapor>`) that drops the virtual DOM for lower overhead. Do not
        depend on it in production until 3.6 ships stable; verify status before adopting.
      
      ## 2. Reactivity pitfalls (where real bugs live)
      
      - **`ref` vs `reactive`:** prefer `ref` for primitives and as the default; `reactive`
        only for objects you won't reassign. **Destructuring a `reactive` object loses
        reactivity** — the extracted variable is a plain snapshot. Use `toRefs`/`toRef`, or
        just use `ref`.
      - **Props destructuring:** since Vue 3.5 the compiler rewrites destructured props to
        keep them reactive (`const { count } = defineProps(...)`), including as the way to
        declare defaults with TypeScript. **Caveat:** passing a destructured prop *into*
        `watch()` or a composable passes a value, not a reactive source — wrap it in a
        getter `() => count` (or `toValue()` inside the composable). Below 3.5, destructuring
        loses reactivity entirely — use `props.count`.
      - **`watch` vs `watchEffect`:** `watch` for explicit sources and precise control (old
        vs new value, lazy by default); `watchEffect` auto-tracks and runs immediately.
        Watching a property of a reactive object needs a getter: `watch(() => obj.id, …)`.
        Deep watches are expensive — Vue 3.5 supports a numeric `deep` depth limit; use
        `once: true` (3.4+) for one-shot.
      - **Watcher/effect lifecycle:** watchers/computed created *synchronously* in `setup`
        auto-dispose on unmount. Ones created **asynchronously** (after an `await`, in a
        callback, in a timer) do **not** — they leak. Wrap detached reactive work in
        `effectScope()` and call `scope.stop()`, or use `onScopeDispose()` in composables.
      - **`shallowRef`/`shallowReactive` for large structures** (big lists, external
        instances, non-reactive class objects): reactivity only at the root, so replace the
        root to trigger updates and treat nested data as immutable. `triggerRef` forces a
        refresh. This is the main Vue perf lever alongside `v-once`/`v-memo` and list
        virtualization (`sota-performance`).
      
      ## 3. Component API design
      
      - **`defineModel()`** (Vue 3.4+) is the modern two-way binding — compiles to the
        `modelValue` prop + `update:modelValue` event, supports multiple/named models and
        modifiers. Prefer it over manually declaring the prop/emit pair.
      - **Type-based `defineProps`/`defineEmits`** is the recommended TypeScript style
        (`defineProps<{ id: string; count?: number }>()`); you cannot mix the type-based and
        runtime-object forms in one call. Emits use the tuple/type syntax (3.3+).
      - **Provide/inject** for cross-cutting dependencies; type the injection key
        (`InjectionKey<T>`). For SSR, remember provide/inject is per-app-instance — which is
        exactly how you avoid cross-request leakage (`rules/06`).
      - **Composables** (`useX`) are Vue's unit of logic reuse — return refs/computed, accept
        getters or `MaybeRefOrGetter` + `toValue()` for flexible inputs, and clean up with
        `onScopeDispose`.
      
      ## 4. Vue-specific XSS and injection
      
      Vue auto-escapes text interpolations and attribute bindings (including in SSR — the
      core `escapeHtml` escapes `" ' & < >`). The sinks are where you leave that protection.
      (General theory: `sota-code-security` rules/05.)
      
      - **`v-html` bypasses escaping** — it sets `innerHTML`. User HTML through `v-html` is
        never safe unless sanitized (allowlist sanitizer) or shown only to its own author in
        a sandboxed context.
      
      ```vue
      <!-- BAD — HIGH: XSS -->
      <div v-html="comment.body" />
      <!-- GOOD — sanitize first, or render as text -->
      <div v-html="sanitize(comment.body)" />   <!-- DOMPurify / server allowlist sanitizer -->
      <div>{{ comment.body }}</div>              <!-- best: no HTML at all -->
      ```
      
      - **Never use untrusted content as a component `template`** — Vue's docs call this
        rule #1: a dynamic template is arbitrary JS execution. Don't build `template:` (or a
        runtime-compiled component) from user input.
      - **Never mount Vue on DOM that contains server-rendered user content.** HTML that is
        safe *as HTML* can be unsafe *as a Vue template* (mustache/`v-` directives in the
        markup get compiled). Mount only on markup you control (hydration-XSS in `rules/06`).
      - **URL injection:** `:href="userUrl"` / `:src` allow `javascript:` and `data:` —
        validate the scheme before binding (sanitize on the backend before storage, per
        Vue's guidance). **Style injection:** binding user data to `:style` enables
        clickjacking-style overlays — bind specific, typed properties, not a raw string.
        Never bind user content to event handlers (`@click="userValue"`).
      
      ## Audit checklist
      
      ```bash
      # HTML-injection sink — each hit needs a sanitizer on the source
      grep -rn 'v-html' --include='*.vue' src components pages
      
      # Dynamic templates / runtime compilation from input (arbitrary JS)
      grep -rnE 'template:\s*[^\x27"]*(\$|props|user|input)' --include='*.vue' --include='*.ts' src
      grep -rn 'compile(' --include='*.ts' src
      
      # URL / style / handler injection
      grep -rnE ':href=|:src=|:style=' --include='*.vue' src | grep -iv 'sanitiz\|allow'
      
      # Reactivity leak/loss patterns
      grep -rnE 'const \{[^}]+\}\s*=\s*reactive\(' --include='*.vue' --include='*.ts' src  # destructuring reactive => lost reactivity
      grep -rnE 'watch(Effect)?\(' --include='*.vue' src | head   # verify async-created ones are scoped
      grep -rn 'ref(' --include='*.ts' src/composables 2>/dev/null # module-level refs? (rules/06 SSR leak)
      ```
      
      - [ ] Every `v-html` fed sanitizer output, not raw user/CMS HTML (or rendered as text instead)?
      - [ ] No component `template`/runtime-compiled component built from user input?
      - [ ] Vue never mounted on DOM containing server-rendered user content?
      - [ ] `:href`/`:src` scheme-validated (no `javascript:`/`data:`); `:style` bound as typed props, not raw strings?
      - [ ] No reactivity lost by destructuring `reactive` (use `toRefs`/`ref`); destructured props not passed bare into `watch`/composables?
      - [ ] Async-created watchers/effects scoped (`effectScope`/`onScopeDispose`) so they don't leak?
      - [ ] `defineModel` for two-way binding; type-based `defineProps`; `shallowRef` for large structures?
      
    • 05-nuxt.md 8.7 KB
      # 05 — Nuxt 4: data fetching, state, server routes, CVEs
      
      Baseline: Nuxt 4.4.x on Nitro 2.x / h3 1.x. Vue fundamentals are in `rules/04`;
      hydration and SSR state in `rules/06`; the cross-framework security boundary in
      `rules/07`. **Re-verify CVE ranges at use time.**
      
      ## 1. Data fetching — pick the right primitive
      
      Getting this wrong causes double-fetches, hydration mismatches, and waterfalls.
      
      - **`$fetch` alone in `setup` fetches twice** — once on the server, once again during
        client hydration — because its result isn't transferred in the payload. Use it only
        for **client-only / event-driven** calls (a button handler, a `POST`).
      - **`useFetch`** is the SSR-safe wrapper: fetches once on the server and transfers the
        result to the client via the payload. The default in components. Keys default to a
        hash of the URL/options; calls sharing a key share the `data`/`error`/`status` refs.
      - **`useAsyncData`** wraps arbitrary async logic (a CMS SDK, multiple calls, custom
        transform) with the same once-and-transfer semantics; give it an explicit key.
      - `lazy: true` (or `useLazyFetch`) doesn't block navigation; `server: false` makes it
        client-only. Use `pick`/`transform` to **shrink the payload** — whatever these
        return is serialized into the HTML (`rules/06`, `rules/07`).
      
      ## 2. SSR-safe shared state — never a module-level ref
      
      - **`useState(key, init)`** is the SSR-friendly `ref`: its value is serialized into
        the payload and restored on the client, and it's shared by key across components.
      - **A module-level `const state = ref()` (outside `setup`) is a cross-request leak**
        on the server — the Nitro process reuses it across all users' requests (one user's
        data served to another) and it grows unbounded (memory leak). Nuxt's docs call this
        out explicitly. This is the single most important Nuxt SSR footgun (`rules/06`).
      - `useState` values must be **serializable** (no classes/functions/symbols). For
        richer state management, **Pinia** (v3) is the recommended store and is
        SSR-safe by design (a fresh store per request).
      
      ## 3. `runtimeConfig` — the server/client secret boundary
      
      ```ts
      export default defineNuxtConfig({
        runtimeConfig: {
          apiSecret: '',                 // SERVER-ONLY — never sent to the client
          public: { apiBase: '/api' },   // PUBLIC — serialized into the payload, visible to all
        },
      })
      ```
      
      - **Only `runtimeConfig.public.*` reaches the client** (it's in the payload).
        Everything at the root is server-only. **A secret under `public` is a client-side
        secret leak (CRITICAL)** — same failure class as `NEXT_PUBLIC_`/`VITE_` (`rules/07`).
      - **Runtime override via env:** a `NUXT_`-prefixed env var overrides the matching key
        (`NUXT_API_SECRET`, `NUXT_PUBLIC_API_BASE`) — but the key must already exist in
        `nuxt.config` to be overridable. `.env` is read at dev/build time but **not** by the
        built production server; provide real env vars in production.
      
      ## 4. Nitro server routes — public API endpoints
      
      Files under `server/api/` and `server/routes/` are Nitro handlers — **public HTTP
      endpoints**, same trust model as any API (`sota-api-design`, `sota-code-security`).
      
      ```ts
      // server/api/posts/[id].delete.ts
      export default defineEventHandler(async (event) => {
        const { user } = await requireUserSession(event)          // 1. authenticate
        const { id } = await getValidatedRouterParams(event, z.object({ id: z.string().uuid() }).parse) // 3. validate
        const post = await db.post.find(id)
        if (post.authorId !== user.id) throw createError({ statusCode: 403 }) // 2. authorize
        await db.post.delete(id)
      })
      ```
      
      - **Validate every input** with `getValidatedQuery` / `readValidatedBody` /
        `getValidatedRouterParams` + a schema (Zod). Unvalidated query/body/params are the
        usual injection and IDOR entry points. Use `createError({ statusCode })` for typed
        responses; an uncaught throw is a 500.
      - **Authenticate + authorize in the handler.** `nuxt-auth-utils` (sealed, encrypted
        session cookies; `requireUserSession`/`setUserSession`; scrypt hashing; OAuth +
        passkeys) is a solid, maintained baseline — it needs a real server (`nuxt build`,
        not `nuxt generate`). Check ownership on every resource to prevent IDOR.
      - **Server-route responses are `JSON.stringify`'d** (unlike the devalue-serialized
        page payload) — return primitives/plain objects and filter them (no raw rows).
      
      ## 5. Hybrid rendering (`routeRules`) and islands
      
      - **`routeRules`** in `nuxt.config` set the render mode per route pattern:
        `ssr: false` (client-only), `prerender: true` (SSG), `swr: <ttl>` (server/proxy
        cache + stale-while-revalidate), `isr: <ttl>` (like swr but pushed to CDN on
        supporting platforms; `isr: true` persists until next deploy). Also `redirect`,
        `headers`, `cors`, `noScripts`. **Security:** an `swr`/`isr`/cached route must not
        serve personalized content — the cache is shared (`rules/06`). A `routeRules`
        matcher that doesn't match the actual (case-sensitive) route can bypass an intended
        rule — the class behind CVE-2026-53721 below.
      - **Server components / islands are experimental** in Nuxt 4 (enable component
        islands; `.server.vue` rendered via `<NuxtIsland>`): single root element, props
        travel as URL query params (keep them small), no route middleware inside an island.
        Given the island advisories below, treat as experimental and don't put authz
        decisions inside island rendering.
      
      ## 6. Nuxt / Nitro / h3 / IPX / devalue CVE reference (verify at use time)
      
      | CVE / advisory | Class | Fixed in |
      |---|---|---|
      | **CVE-2025-27415** (GHSA-jvhm-gjrh-3h93) | Nuxt CDN **cache poisoning DoS** via a `?…_payload.json`-style query rendering the route as JSON, High 7.5 | Nuxt **3.16.0** |
      | **CVE-2026-53721** (GHSA-mm7m-92g8-7m47) | `routeRules` **middleware bypass** via case-sensitivity mismatch, High | Nuxt **4.4.7 / 3.21.7** |
      | CVE-2026-53722 (GHSA-934w-87qh-qr26) | Reflected **XSS in `<NuxtLink>`** via `javascript:`/`data:` URLs | Nuxt 4.4.7 / 3.21.7 |
      | GHSA-hg3f-28rg-4jxj | Route middleware **not enforced** rendering `.server.vue` via `/__nuxt_island/…` | see advisory (2026-05) |
      | CVE-2025-54387 (GHSA-mm3p-j368-7jcr) | **IPX path traversal** (prefix-match bypass) — the `@nuxt/image` optimizer | IPX **1.3.2 / 2.1.1 / 3.1.1** |
      | CVE-2026-33128 (GHSA-22cc-p3c6-wpvm) + follow-ups | **h3 SSE injection** via unsanitized newlines (High); serveStatic path traversal; middleware bypass | h3 **1.15.6 / 2.0.1-rc.15** |
      | CVE-2025-57820 (GHSA-vj54-72f3-p5jv) | **devalue prototype pollution** on `parse` (the Nuxt payload deserializer), High | devalue **5.3.2** |
      | CVE-2026-30226 (GHSA-cfw5-2vxh-hr84) + DoS advisories | devalue prototype pollution / parse DoS | devalue **5.6.4** (+ later) |
      
      - **`nuxt-security` module** is the maintained hardening layer: OWASP-pattern security
        headers, CSP (with nonce support for SSR — verify the CSP docs for your mode), rate
        limiting, request-size limits, CORS, allowed-methods, XSS input validation, CSRF.
        Strongly consider it for any Nuxt app exposed to the internet.
      
      ## Audit checklist
      
      ```bash
      # Exact versions vs the CVE table
      node -e "const p=require('./package.json');console.log(p.dependencies?.nuxt||p.devDependencies?.nuxt)"
      grep -E '"(nuxt|nitropack|h3|ipx|@nuxt/image|devalue|pinia)"' package.json
      
      # Secret under public runtimeConfig (CRITICAL)
      grep -rnA8 'runtimeConfig' nuxt.config.* | grep -iE 'public' -A6 | grep -iE 'secret|key|token|password'
      
      # Module-level refs / state outside setup (cross-request leak)
      grep -rnE '^(export )?const \w+\s*=\s*(ref|reactive)\(' --include='*.ts' composables server utils 2>/dev/null
      
      # $fetch in setup (double fetch), server routes without validation
      grep -rn '\$fetch(' --include='*.vue' pages components | grep -v useFetch
      grep -rLn 'getValidated\|readValidatedBody\|requireUserSession\|\.parse(' server/api server/routes 2>/dev/null
      
      # routeRules that cache — must not be personalized
      grep -rnE 'swr|isr|prerender|ssr:\s*false' nuxt.config.*
      
      # NuxtLink / URL sinks
      grep -rn ':to=\|:href=' --include='*.vue' pages components | grep -iv 'sanitiz'
      ```
      
      - [ ] Nuxt/Nitro/h3/IPX/devalue versions patched against the table (esp. CVE-2025-27415, CVE-2026-53721, devalue pollution)?
      - [ ] No secret under `runtimeConfig.public`; server secrets at the root only?
      - [ ] No module-level `ref`/`reactive` state outside `setup` (cross-request leak); `useState`/Pinia used instead?
      - [ ] Every `server/api` handler authenticates, authorizes (ownership), and schema-validates input; responses filtered?
      - [ ] `useFetch`/`useAsyncData` (not bare `$fetch`) for SSR data; payload shrunk via `pick`/`transform`?
      - [ ] No `swr`/`isr`/`prerender` route serving personalized content; `routeRules` matchers actually match?
      - [ ] `nuxt-security` (or equivalent headers/CSP/rate-limit) in place for internet-facing apps?
      
    • 06-ssr-hydration.md 9.8 KB
      # 06 — SSR & hydration: mismatches, serialization, caching, CSP
      
      Cross-cutting concerns of server rendering, shared by Next and Nuxt. Framework
      specifics are in `rules/03`/`rules/05`; the consolidated security boundary in
      `rules/07`.
      
      ## 1. Hydration mismatches — cause and correct fix
      
      Hydration is the client attaching event handlers to server-rendered HTML, assuming the
      two render trees are identical. When they diverge:
      
      - **React** may recover from some mismatches but gives *no guarantee* attribute
        differences are patched, and if a mismatch forces it, React **discards the server
        HTML and re-renders the whole root on the client** — losing the SSR benefit and
        flashing. Treat every mismatch as a bug.
      - **Vue/Nuxt** logs a mismatch warning and patches the DOM toward the client render;
        invalid HTML nesting silently corrupts the tree.
      
      **Common causes** (per react.dev and vuejs.org):
      
      - Non-deterministic values in render: `Date.now()`/`new Date()`, `Math.random()`,
        locale/timezone-dependent formatting, `crypto.randomUUID()`.
      - Branching on `typeof window`, `matchMedia`, `localStorage`, `navigator` during
        render (server and client take different branches).
      - **Invalid HTML nesting** (`<p><div>`, `<a><a>`, a `<div>` inside `<table>` without
        `<tbody>`): the browser's parser auto-corrects, so the client tree no longer matches
        the server string.
      - Browser extensions mutating the DOM before hydration (can't fully control; don't let
        it mask real mismatches).
      
      **Correct fixes:**
      
      - Make render **deterministic**. Move browser-only reads into `useEffect`
        (React) / `onMounted` (Vue) so they run after hydration, or gate with a mounted
        flag / `<ClientOnly>` (Nuxt) / dynamic import with `ssr: false`.
      - **Stable IDs:** React `useId` (with `identifierPrefix` on `hydrateRoot` for multiple
        roots) and Vue 3.5 `useId()` — never random IDs across the boundary.
      - For genuinely unavoidable divergence (a live timestamp), scope the suppression as
        narrowly as possible: React `suppressHydrationWarning` (one level deep, intended for
        exactly this), Vue 3.5 `data-allow-mismatch`. These silence the warning for *that
        node only* — never blanket them, and never use them to paper over a real bug.
      - **Security:** never "resolve" a mismatch by injecting server-side user-controlled
        HTML into the tree. Browser parser normalization of malformed markup is the same
        parser-differential class that drives mutation-XSS — escape/sanitize instead of
        hand-patching the DOM to match. (Sanitizer guidance: `rules/02`/`rules/04`.)
      
      ## 2. SSR state serialization — an XSS sink
      
      To avoid double-fetching, SSR frameworks embed fetched state in the HTML as an inline
      `<script>`. Done naively this is a script-injection hole.
      
      - **Naked `JSON.stringify` into a `<script>` is XSS.** JSON does not escape `<`, so a
        value containing `</script>` closes the tag early and injects markup; `<!--` and
        `<script` can also break parsing, and `U+2028`/`U+2029` break older JS string
        parsers. The fix is to escape `<`, `>`, `&`, and the line separators to `\uXXXX`
        before embedding (or set the data via `<script type="application/json">` + `textContent`,
        parsed with `JSON.parse`).
      
      ```js
      // BAD — HIGH: state contains user data → </script> breakout
      html += `<script>window.__DATA__=${JSON.stringify(state)}</script>`;
      // GOOD — escape the HTML-significant characters first
      const safe = JSON.stringify(state).replace(/</g,'\\u003c').replace(/>/g,'\\u003e')
        .replace(/&/g,'\\u0026').replace(/\u2028/g,'\\u2028').replace(/\u2029/g,'\\u2029');
      ```
      
      - **Prefer the framework serializer** — you rarely hand-roll this:
        - **Nuxt** serializes the payload with **devalue** (handles `Date`/`Map`/`Set`/refs
          and escapes `</script>` + line separators). But devalue's *parse* side has had
          prototype-pollution CVEs (CVE-2025-57820, CVE-2026-30226) and DoS advisories — keep
          it patched (`rules/05`).
        - **Next** embeds the RSC/flight payload via `self.__next_f.push([...])`.
        - **`serialize-javascript`** (used to embed functions/regex): CVE-2020-7660 (RCE,
          fixed 3.1.0) and CVE-2024-11831 (XSS via unescaped URL objects, fixed **6.0.2**) —
          if it's in the tree, verify the version.
      - **Only serializable data belongs in the payload**, and *everything* in it is public
        — never let a secret, token, or full DB row reach `useState`/a client prop
        (`rules/07`).
      
      ## 3. Cross-request state pollution
      
      On the server the module graph is loaded **once per process** and reused for every
      request. Module-level mutable state is therefore shared across all users.
      
      - **The bug:** `let currentUser` / `const cart = reactive([])` / a singleton client
        holding per-request data at module scope. Under load, one user sees another's data,
        and memory grows unbounded. This is a confidentiality breach, not just a leak.
      - **React/Next:** don't hold request data in module globals; use request-scoped APIs
        (`cookies()`/`headers()`, React `cache()` for per-request memoization, the DAL).
      - **Vue/Nuxt:** create fresh app/router/store instances per request (Nuxt does this
        for you); use `useState`/Pinia, never a module-level `ref` (`rules/05`). Share
        request-scoped values via app-level `provide`/`inject`, not module scope.
      - Audit any module-level `let`/mutable singleton in server-reachable code.
      
      ## 4. Caching personalized SSR safely
      
      Caching is where SSR bugs become cross-user data leaks.
      
      - **Personalized (auth/cookie-dependent) responses must be `Cache-Control: private`**
        (browser only) or uncached — never `public`/`s-maxage` at a shared CDN.
      - **`Vary` is not a reliable isolation mechanism at CDNs.** Cloudflare ignores `Vary`
        values; CloudFront strips `Vary` before returning. If correctness depends on the
        cache keying by a header, verify your CDN actually honors it — prefer explicit
        per-user cache keys or no shared caching.
      - **Web cache deception / poisoning** (PortSwigger "Gotta cache 'em all", 2024) exploit
        URL-parsing differences between CDN and origin so a crafted path is cached as a
        static asset while the origin served a private page. Both Next
        (CVE-2024-46982, CVE-2025-49005, CVE-2025-32421) and Nuxt (CVE-2025-27415) have
        shipped cache-poisoning CVEs — keep patched and don't hand a CDN an ambiguous
        cache key. Next's RSC payload uses a `Rsc:`/`_rsc=` scheme that has been a poisoning
        vector; a request missing the buster but carrying the header can poison HTML with an
        RSC payload.
      - **ISR/SWR** trade freshness for speed — only for non-personalized content, and think
        through the stale window (`rules/01`, `rules/05`).
      
      ## 5. CSP with streaming SSR
      
      A strict, nonce/hash-based CSP is the highest-leverage defense-in-depth for these apps
      — but it interacts with rendering.
      
      - **Next (official guide):** generate a per-request nonce in `proxy.ts`, set it on the
        CSP header and an `x-nonce` request header; Next applies it to its own scripts. Use
        `script-src 'self' 'nonce-<n>' 'strict-dynamic'`. **Nonces force dynamic rendering**
        — they're incompatible with static generation, ISR, and PPR (there's no request at
        build time). Opt a page into dynamic with `await connection()`. The static-friendly
        alternative is experimental hash-based CSP via SRI (App Router). Dev needs
        `'unsafe-eval'` (not prod).
      - **Nuxt:** the `nuxt-security` module supplies CSP — runtime nonces for SSR (Nitro
        sets the header), build-time SHA hashes for SSG (`<meta>`, no server) — both with
        `'strict-dynamic'` and no `'unsafe-inline'`.
      - Without nonces/hashes, framework output typically requires `'unsafe-inline'`, which
        neuters CSP against injected inline scripts. If you're going to have a CSP, wire the
        nonce/hash — a CSP with `'unsafe-inline'` on `script-src` is close to no CSP.
      - CSP is defense-in-depth over escaping/sanitization (`rules/02`/`rules/04`), never a
        replacement. General CSP/headers depth: `sota-code-security` rules/05,
        `sota-network-security`.
      
      ## Audit checklist
      
      ```bash
      # Non-determinism in render (hydration bugs)
      grep -rnE '(Date\.now|new Date|Math\.random|crypto\.randomUUID)\(' --include='*.tsx' --include='*.vue' src app components pages | grep -v useEffect
      grep -rnE 'typeof window|localStorage|matchMedia|navigator\.' --include='*.tsx' --include='*.vue' src app pages
      
      # Blanket mismatch suppression (smell if widespread)
      grep -rn 'suppressHydrationWarning\|data-allow-mismatch' --include='*.tsx' --include='*.vue' src app pages
      
      # Hand-rolled state serialization into <script>
      grep -rnE 'JSON\.stringify' --include='*.ts' --include='*.tsx' server app | grep -i 'script\|__DATA__\|innerHTML'
      grep -E '"serialize-javascript"' package.json   # verify >=6.0.2
      
      # Cross-request state pollution: module-level mutable state in server code
      grep -rnE '^(export )?(let|const) \w+\s*=\s*(reactive|ref|new |\[\]|\{\})' --include='*.ts' server lib composables utils 2>/dev/null
      
      # Caching / CSP
      grep -rn 'Cache-Control\|s-maxage\|Vary' --include='*.ts' server app middleware.* proxy.*
      grep -rn "Content-Security-Policy\|nonce\|strict-dynamic\|nuxt-security" --include='*.ts' app proxy.* middleware.* nuxt.config.*
      ```
      
      - [ ] Render deterministic — no `Date`/random/`window`/locale branching outside effects/`onMounted`; stable `useId`?
      - [ ] Mismatch suppression scoped to individual unavoidable nodes, never blanket, never patched with injected user HTML?
      - [ ] SSR state serialized via the framework serializer or `<`-escaped JSON (no naked `JSON.stringify` into `<script>`); `serialize-javascript` ≥ 6.0.2; devalue patched?
      - [ ] No secret/token/full row in the serialized payload (`useState`/client props)?
      - [ ] No module-level mutable state in server-reachable code (cross-request leak)?
      - [ ] Personalized responses `Cache-Control: private`/uncached; not relying on `Vary` at the CDN; framework patched against cache-poisoning CVEs?
      - [ ] CSP present and nonce/hash-based (not `'unsafe-inline'` on `script-src`), with the dynamic-rendering trade-off understood?
      
    • 07-security.md 8.6 KB
      # 07 — Framework security: boundary, authz, SSRF, CVEs
      
      The security pass for both stacks. This consolidates the framework-specific angles;
      generic web-appsec theory (injection classes, session/JWT mechanics, crypto) lives in
      `sota-code-security`, secrets handling in `sota-secrets-management`, supply chain in
      `sota-devsecops`. **Re-verify every CVE at use time.**
      
      ## 1. The server/client secret boundary
      
      The one boundary unique to these frameworks, and the most common leak.
      
      - **Build-time-inlined public env is public forever:** `NEXT_PUBLIC_*`, Nuxt
        `runtimeConfig.public.*`, and Vite `VITE_*` are substituted into the client bundle at
        build time. **A secret in any of them is a client-side secret leak (CRITICAL)** —
        and rotating it means a rebuild, not just a config change. Server secrets go in
        unprefixed env / root `runtimeConfig`, read only in server code.
      - **Everything crossing server→client is public:** RSC props (Next), the serialized
        payload (`useState`/`useFetch` data in Nuxt), and anything in an inline `<script>`.
        Pass **minimal DTOs**, never raw DB rows, tokens, internal flags, or `process.env`.
      - **Enforce the boundary mechanically:** `import 'server-only'` (Next/bundler) makes a
        server module a build error if imported by client code; keep data access and secrets
        behind it. In Nuxt, keep secrets in `server/` and root `runtimeConfig`.
      - **Production source maps** can re-expose "server" logic and comments if uploaded
        publicly — ship them to your error tracker privately, not to the CDN.
      
      ## 2. Authorization placement (post-CVE-2025-29927)
      
      The middleware-bypass CVE (CVE-2025-29927) made the lesson concrete: **a single authz
      checkpoint at the edge is not enough.**
      
      - **Middleware / `proxy.ts` (Next) and route middleware (Nuxt) are optimizations**, not
        the security boundary. They can be bypassed (a spoofed header; a matcher that stops
        covering a route; the Nuxt case-sensitivity bypass CVE-2026-53721) and don't re-run
        where you assume.
      - **Authorize next to the data.** Every Server Action, Route Handler, RSC data read,
        and Nitro handler independently: (1) authenticates, (2) authorizes the *specific
        resource* (ownership/role), (3) validates input. A Data Access Layer (Next) or a
        shared `requireUserSession` + policy helper (Nuxt) is how you avoid forgetting.
      - **IDOR is the dominant framework authz bug:** an action/route that takes an `id` and
        acts on it without checking the caller owns it. Test for it (`sota-testing` authz
        tests). Never trust a client-supplied role/flag (`?isAdmin=true`, a hidden field).
      - **Layouts don't re-authorize on client navigation** (Next) — don't put the only
        check there.
      
      ## 3. SSRF surfaces specific to SSR apps
      
      Server-side rendering means the server makes outbound requests — attacker-influenced
      URLs become SSRF.
      
      - **Image optimizers:** `next/image` `remotePatterns`/`domains` with a `**` wildcard,
        and IPX/`@nuxt/image` (path-traversal CVE-2025-54387), turn the optimizer into a
        proxy to internal URLs and the cloud metadata endpoint (169.254.169.254). Allowlist
        explicit hosts, protocols, ports, and path prefixes — and note the optimizer follows
        redirects from an allowed host *without re-validating*, so an open redirect on an
        allowlisted domain becomes SSRF.
      - **User-URL fetchers:** og-image/link-preview/"import from URL" features fetch a
        user-supplied URL server-side. Validate the host against an allowlist, block private
        ranges and the metadata IP, disable/limit redirects, and set timeouts
        (`sota-code-security` rules/09, `sota-network-security` for egress control).
      - **Header trust:** building absolute URLs or redirects from `Host`/`X-Forwarded-Host`
        (Next SSRF CVE-2024-34351, CVE-2025-57822; Nuxt navigate advisories) lets an attacker
        redirect server fetches. Pin a canonical base URL from config, don't reflect the Host
        header. Never pass unsanitized inbound headers into `NextResponse.next()`/redirects.
      - **Open redirects:** validate `redirect`/`next`/`returnTo` targets against a
        same-origin/allowlist check before redirecting.
      
      ## 4. Consolidated framework CVE reference (verify ranges at use time)
      
      Fingerprint exact versions from the lockfile; unpatched criticals are findings on
      their own. Full detail in `rules/03` (Next/React) and `rules/05` (Nuxt/Nitro/h3).
      
      **React / Next.js**
      
      - **CVE-2025-55182 "React2Shell"** — RSC deserialization **RCE, CVSS 10.0**;
        react-server-dom-* fixed 19.0.1 / 19.1.2 / 19.2.1. Next surface **CVE-2025-66478**
        fixed on every 15.x/16.x line (e.g. 15.5.7 / 16.0.7); **rotate secrets if it ran
        unpatched.** Follow-up DoS/exposure: CVE-2025-55184/-55183/-67779, CVE-2026-23864.
      - **CVE-2025-29927** — middleware auth bypass, CVSS 9.1; fixed
        12.3.5/13.5.9/14.2.25/15.2.3.
      - Cache poisoning: CVE-2024-46982 (13.5.7/14.2.10), CVE-2025-49005 (15.3.3),
        CVE-2025-32421. SSRF: CVE-2024-34351 (14.1.1), CVE-2025-57822 (14.2.32/15.4.7),
        GHSA-c4j6-fc7j-m34r (WebSocket). CSP-nonce XSS: CVE-2026-44581 (15.5.16/16.2.5).
        next/image: CVE-2025-55173 / CVE-2025-57752 (14.2.31/15.4.5). DoS: CVE-2024-56332.
      
      **Vue / Nuxt / Nitro / h3 / IPX / devalue**
      
      - Nuxt cache-poisoning DoS **CVE-2025-27415** (3.16.0); `routeRules` bypass
        **CVE-2026-53721** (4.4.7/3.21.7); `<NuxtLink>` XSS CVE-2026-53722; island authz
        advisories (GHSA-hg3f-28rg-4jxj).
      - IPX path traversal **CVE-2025-54387** (1.3.2/2.1.1/3.1.1).
      - h3 SSE injection **CVE-2026-33128** + serveStatic traversal / middleware bypass
        (1.15.6 / 2.0.1-rc.15).
      - devalue prototype pollution **CVE-2025-57820** (5.3.2), **CVE-2026-30226** (5.6.4) +
        DoS advisories. serialize-javascript CVE-2024-11831 (6.0.2).
      
      The pattern across all of these: **the fix is almost always "upgrade."** Automated
      dependency updates + a fast patch path is the actual control (`sota-devsecops`).
      
      ## 5. Framework security hygiene
      
      - **CSP** nonce/hash-based, not `'unsafe-inline'` (`rules/06`); `nuxt-security` or a
        Next `proxy.ts` header layer. Plus the standard headers (HSTS, `nosniff`,
        `frame-ancestors`) — `sota-code-security` rules/05.
      - **Supply chain:** these apps have deep dependency trees (a framework pulls hundreds of
        transitive packages). Lockfile committed, `npm audit`/`osv-scanner` in CI, provenance
        where available (`sota-devsecops`). Client-shipped dependencies are also an XSS
        surface (a compromised npm package runs in your users' browsers).
      - **Error handling:** don't leak stack traces / internal paths to the client in
        production; don't render user-controlled error text as HTML (`rules/02`).
      - **Rate limiting** on Server Actions / Route Handlers / Nitro routes — they're public
        endpoints (`sota-api-design` rules/07).
      
      ## Audit checklist
      
      ```bash
      # Public-env secret leak (CRITICAL)
      grep -rnE '(NEXT_PUBLIC_|VITE_)[A-Z_]*(SECRET|KEY|TOKEN|PASSWORD|PRIVATE)' --include='*.ts' --include='*.tsx' --include='*.vue' .
      grep -rnA8 'runtimeConfig' nuxt.config.* | grep -iE 'public' -A6 | grep -iE 'secret|key|token'
      
      # Server->client exposure & the server-only guard
      grep -rn "import 'server-only'\|server/" app lib server | head
      grep -rnE 'process\.env\.' --include='*.tsx' --include='*.vue' app components pages | grep -iv 'NEXT_PUBLIC\|NODE_ENV'
      
      # Authz only at the edge? enumerate actions/handlers and check each
      grep -rn "'use server'" app lib; ls -1 app/**/route.ts server/api/**/*.ts 2>/dev/null
      
      # SSRF surfaces
      grep -rn 'remotePatterns\|images:\s*{\|ipx\|@nuxt/image' next.config.* nuxt.config.* | grep -n '\*\*\|domains'
      grep -rnE '\$?fetch\(|ofetch\(|axios\.|got\(' --include='*.ts' server app | grep -iE 'req\.|query|params|headers|host'
      grep -rnE 'X-Forwarded-Host|req\.headers\.host|getRequestHost' --include='*.ts' server app proxy.* middleware.*
      
      # CVE fingerprint
      grep -E '"(react|react-dom|react-server-dom-webpack|next|nuxt|nitropack|h3|ipx|devalue|serialize-javascript)"' package.json
      ```
      
      - [ ] No secret in `NEXT_PUBLIC_`/`VITE_`/`runtimeConfig.public`; server secrets behind `server-only`/`server/`?
      - [ ] No raw rows/tokens/`process.env` crossing server→client; minimal DTOs only?
      - [ ] Every Server Action / Route Handler / Nitro route authenticates, authorizes the specific resource (no IDOR), and validates input — not relying on edge middleware?
      - [ ] `next/image`/IPX `remotePatterns` allowlisted to explicit hosts (no `**`); user-URL fetchers block private ranges + metadata IP + redirects?
      - [ ] Absolute URLs/redirects built from config, not reflected `Host`/`X-Forwarded-Host`; redirect targets allowlisted?
      - [ ] All framework + loader deps (react-server-dom, next, nuxt, h3, ipx, devalue, serialize-javascript) patched against §4; automated updates on?
      - [ ] Nonce/hash CSP + standard security headers; rate limiting on public endpoints; production errors not leaked as HTML?
      
  • SKILL.md 10.6 KB
    ---
    name: sota-web-frameworks
    description: >-
      State-of-the-art engineering rules (2026) for the JavaScript SSR meta-frameworks:
      React 19 + Next.js (App Router, React Server Components, Server Actions) and Vue 3 +
      Nuxt 4 (Nitro server routes, composables) — plus the cross-cutting concerns of
      server rendering: hydration correctness, SSR state serialization, the server/client
      trust boundary, and framework-specific security and CVEs. Use when building or
      auditing any React/Next or Vue/Nuxt app — components, RSC/client boundaries, Server
      Actions or Nitro routes, data fetching and caching, hydration mismatches, SSR/SSG/ISR
      strategy, or framework CVE exposure. Complements sota-javascript-typescript,
      sota-frontend-design, sota-code-security, and sota-performance. Trigger keywords:
      React, Next.js, App Router, React
      Server Components, RSC, Server Actions, use client, use server, Vue, Nuxt, Nitro,
      Pinia, composable, script setup, SSR, hydration, hydration mismatch, use cache, PPR,
      ISR, proxy.ts, middleware, useFetch, useState, runtimeConfig, CSP nonce, devalue.
    ---
    
    # SOTA Web Frameworks (2026)
    
    ## Purpose
    
    This skill encodes the 2026 state of the art for the two dominant JavaScript SSR
    stacks — **React + Next.js** and **Vue + Nuxt** — and the server-rendering concerns
    they share. It is deliberately framework-specific: the traps that matter here
    (the RSC server/client boundary, Server Actions as public endpoints, hydration
    mismatches, SSR state leaking across requests, `runtimeConfig`/`NEXT_PUBLIC_`
    secret boundaries) do not exist at the plain-language level.
    
    Two modes:
    
    - **BUILD** — writing or modifying components, routes, and data-fetching to this standard.
    - **AUDIT** — reviewing an existing app and reporting findings.
    
    Read SKILL.md fully; load `rules/*.md` on demand per the index below. **Verify every
    version/CVE claim at use time** — this file's facts were primary-sourced 2026-07 but
    framework security moves weekly (see the 2025-12 React Server Components RCE).
    
    ## Scope boundaries (what lives elsewhere)
    
    This skill stacks *on top of* the general skills — load them together, don't duplicate:
    
    - **`sota-javascript-typescript`** — strict `tsconfig`, type design, promises,
      Node hardening, npm supply chain. The *language*; this skill is the *framework*.
    - **`sota-frontend-design`** — visual design, layout, components-as-UX, WCAG 2.2
      accessibility, motion. This skill covers component *engineering*, not *design*.
    - **`sota-code-security`** — generic XSS/CSRF/SSRF/authn/authz/crypto theory. This
      skill covers the *framework-specific* expression of those (RSC data exposure,
      Server Action authz, `v-html`, next/image SSRF).
    - **`sota-performance`** rules/06 — Core Web Vitals, bundle budgets, image/font
      loading. This skill covers render-strategy choice (SSR/SSG/ISR/PPR) and hydration.
    - **`sota-api-design`** — REST/GraphQL contract design for the routes themselves.
    
    ## BUILD mode
    
    1. **Establish context first.** Read `package.json` for the exact React/Next or
       Vue/Nuxt majors and the render mode in use (App vs Pages Router; Nuxt SSR vs
       `ssr: false`). Match the project's baseline — no Server Actions on a Pages-Router
       app, no `defineModel` below Vue 3.4. Confirm the versions are supported and
       patched against the CVE tables in `rules/03`/`rules/05`. (`rules/01`)
    2. **Default to the current idiom:** React function components + hooks (let the React
       Compiler memoize — don't hand-write `useMemo` everywhere); Vue Composition API with
       `<script setup>`. Server Components by default in Next App Router, `"use client"`
       only at the leaves that need interactivity. (`rules/02`, `rules/04`)
    3. **The server/client boundary is a security boundary, not just a perf one.** Every
       prop crossing server→client is serialized into the HTML/RSC payload and is public.
       Authorization lives *next to the data* (a Data Access Layer / validated server
       route), never only in middleware or a layout. (`rules/03`, `rules/07`)
    4. **Treat every Server Action and Nitro route as a public, unauthenticated HTTP
       endpoint** until it validates input and checks authz itself — even if it looks
       internal or is never imported. (`rules/03`, `rules/05`, `rules/07`)
    5. **Get hydration right by construction:** deterministic render (no `Date.now()`,
       `Math.random()`, or `window` in render), stable IDs via `useId`, SSR-safe shared
       state (`useState`/per-request instances, never module-level refs). (`rules/06`)
    6. **Serialize SSR state safely** (framework serializer or escaped JSON, never naked
       `JSON.stringify` into `<script>`) and **wire CSP** (nonce or hash) knowing it forces
       dynamic rendering in Next. (`rules/06`, `rules/07`)
    7. **Tests accompany code** (`sota-testing`): component tests plus at least one test
       that exercises the server/client boundary or a server route's authz.
    
    ## AUDIT mode
    
    1. **Fingerprint + patch-check first.** Pin exact framework/loader versions from the
       lockfile and diff them against the CVE tables in `rules/03` and `rules/05`. An
       unpatched React2Shell (CVE-2025-55182) or middleware bypass (CVE-2025-29927) is
       CRITICAL on its own, before any code is read.
    2. **Trace the trust boundary.** Grep `"use client"`/`"use server"`, `defineProps`,
       `runtimeConfig`, `NEXT_PUBLIC_`/`NUXT_PUBLIC_`; find where server data crosses to
       the client and where authz is enforced. Middleware/layout-only authz is a finding.
    3. **Run each relevant rules file's Audit checklist** (grep-driven), then read for
       design: hydration determinism, SSR state isolation, caching of personalized pages.
    4. **Verify every finding** — a `v-html` fed a constant is not XSS; a Server Action
       that re-checks the session is not IDOR. Note mitigations already present.
    
    ### Severity conventions
    
    | Severity | Meaning | Examples |
    |---|---|---|
    | CRITICAL | Exploitable now / RCE / data loss | Unpatched RSC deserialization RCE; middleware-bypass auth on the only authz layer; secret in `NEXT_PUBLIC_`/`public` runtimeConfig; `unserialize`-class sink |
    | HIGH | Exploitable with preconditions | Server Action / Nitro route with no authz (IDOR); SSRF via next/image `**` or user-URL fetch; `dangerouslySetInnerHTML`/`v-html` of user data; whole-DB-row as a client prop |
    | MEDIUM | Real but bounded, or reliability | Personalized SSR page cacheable at a shared CDN; hydration mismatch on user data; missing CSP; module-level SSR state; unpatched non-critical CVE |
    | LOW | Deviation from SOTA | Pages Router for greenfield; hand-rolled memoization vs React Compiler; `$fetch` double-fetch in setup; Options API for a new app |
    | INFO | Worth knowing | Newer render mode available; migration opportunities |
    
    ### Finding format
    
    ```
    file:line | rule violated (rules/NN §S) | severity | effort | fix
    ```
    
    Effort: trivial · small · medium · large. Group by severity, CRITICAL first.
    Borderline severities state the deciding assumption; unconfirmed findings are
    marked "needs verification", never asserted. End with per-severity counts, the
    sweep commands run, and explicit "checked and clean" areas.
    
    ## Rules index
    
    | File | Read this when... |
    |---|---|
    | `rules/01-baseline.md` | choosing or verifying a stack and version floor (React/Next/Vue/Nuxt support+EOL matrix), picking a render mode (CSR/SSR/SSG/ISR/PPR), project setup, React Compiler |
    | `rules/02-react.md` | writing React components: hooks rules, `useId`/`useEffect`/refs, Suspense & error boundaries, `use()`/Actions/`useActionState`, memoization & the React Compiler, `dangerouslySetInnerHTML` |
    | `rules/03-nextjs.md` | Next.js App Router: Server vs Client Components, Server Actions, the caching model (`use cache`/Cache Components/PPR/ISR/`revalidate`), `proxy.ts`/middleware, the Data Access Layer, and Next CVEs |
    | `rules/04-vue.md` | writing Vue: Composition API & `<script setup>`, reactivity pitfalls (props destructure, `shallowRef`, watchers, `effectScope`), `defineModel`, TypeScript, and Vue XSS (`v-html`, template injection) |
    | `rules/05-nuxt.md` | Nuxt 4: data fetching (`useFetch`/`useAsyncData`/`$fetch`), `useState`, `runtimeConfig`, Nitro server routes & auth, `routeRules`/hybrid rendering, islands, and Nuxt/Nitro/h3/IPX CVEs |
    | `rules/06-ssr-hydration.md` | anything SSR: hydration mismatches & determinism, SSR state-serialization XSS, cross-request state pollution, caching personalized pages safely, CSP with streaming SSR |
    | `rules/07-security.md` | a security pass: the server/client secret boundary, authorization placement (post-CVE-2025-29927), SSRF surfaces, the consolidated framework CVE reference, and supply-chain notes |
    
    ## Top-10 non-negotiables
    
    1. **Run supported, patched majors.** React ≥ 19.x, Next ≥ 15.x (16.x current), Vue
       ≥ 3.5, Nuxt ≥ 4.x (Nuxt 3 security-only until 2026-07-31). Cross-check the CVE
       tables — an unpatched RSC RCE (CVE-2025-55182) or middleware bypass
       (CVE-2025-29927) is a ship-stopper. (`rules/01`, `rules/03`, `rules/05`)
    2. **Authorization lives next to the data, never only in middleware or a layout.**
       `proxy.ts`/middleware is an optimization, not the auth layer; each Server Action,
       Route Handler, and Nitro route re-checks authn + authz. (`rules/03`, `rules/07`)
    3. **Every prop that crosses server→client is public.** It's serialized into the RSC
       payload / HTML. Pass minimal DTOs, never raw DB rows, tokens, or `process.env`.
       (`rules/03`, `rules/07`)
    4. **Server Components by default; `"use client"` only where interactivity requires
       it.** Keep secrets and data access in Server Components / server-only modules
       (`import 'server-only'`). (`rules/02`, `rules/03`)
    5. **Secrets never reach the client bundle.** Nothing sensitive in `NEXT_PUBLIC_*`,
       `VITE_*`, or `runtimeConfig.public` — those are inlined at build time. (`rules/07`)
    6. **Validate every server-route/action input with a schema** (`getValidatedBody`/Zod;
       never trust `formData`, `searchParams`, or headers), and check resource ownership
       to prevent IDOR. (`rules/03`, `rules/05`)
    7. **Hydrate deterministically:** no `Date`/random/`window` in render, stable IDs via
       `useId`, SSR-safe shared state — and never "fix" a mismatch by injecting
       server-side user HTML. (`rules/06`)
    8. **Serialize SSR state safely.** Framework serializer (devalue) or `<`-escaped JSON
       in `<script>`; naked `JSON.stringify` into a script tag is XSS. (`rules/06`)
    9. **User HTML is dangerous HTML.** `dangerouslySetInnerHTML`/`v-html` only on
       sanitizer output; validate `href`/URL schemes (`javascript:`); lock `next/image`
       `remotePatterns` to explicit hosts (no `**`). (`rules/02`, `rules/04`, `rules/07`)
    10. **Don't cache personalized SSR at a shared cache.** `Cache-Control: private` for
        per-user pages; know that CDNs drop `Vary`; wire CSP nonces knowing they force
        dynamic rendering. (`rules/06`)
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related