Web security

Web security

The threat model of a Nuxt application — where untrusted input enters, what the browser protects and what it does not, the OWASP Top 10 (2025) as vocabulary — and how the pages on XSS, CSP and headers, authentication and CSRF, server-side risks and the software supply chain fit together.

A fullstack Nuxt developer owns both halves of the attack surface: the browser, where XSS, CSRF and leaked tokens live, and the Nitro server, where injection, SSRF and broken authorisation live. Interviewers rarely ask for exploits; they ask what you do by default, what you check in review, and what you would say to a product manager who wants to render user-provided HTML. This section is that default posture.

Know

  • Think in trust boundaries. Untrusted input enters from the URL, request bodies and headers, cookies, uploaded files, third-party APIs and webhooks, storage, postMessage, and your own database when it holds user content. Every boundary needs validation on entry and encoding on exit.
  • What the browser gives you: the same-origin policy and CORS (protecting users' data from other sites), SameSite cookies (most CSRF), HttpOnly (XSS cannot read the session), CSP (limits what injected code can do), HTTPS with HSTS. What it does not give you: authorisation, input validation, protection from your own dependencies.
  • What Vue and Nuxt give you: template interpolation and attribute binding are escaped; the payload is serialised safely; runtimeConfig keeps secrets server-side; readValidatedBody validates inputs; nuxt-auth-utils seals sessions; production error pages hide stack traces. What they do not give you: safety in v-html, javascript: URLs, user templates, or any server route you forget to protect.
  • OWASP Top 10 (2025 edition) is the shared vocabulary: broken access control, security misconfiguration, software supply chain failures (new), cryptographic failures, injection, insecure design, authentication failures, software and data integrity failures, logging and alerting failures, and mishandling of exceptional conditions (new). Two of the ten are about how you build and operate, not about code.
  • Defence in depth and least privilege: validate and encode and set CSP; scope tokens and API keys minimally; run CI with least-privilege permissions; assume one layer will fail.
  • Security is a process: dependency updates on a cadence, security releases applied quickly (Nuxt shipped several on the 4.4 and 4.5 lines), threat modelling for new features, a way for researchers to report issues, logs that let you notice an incident.

How the pages fit

PageQuestion it answers
XSS and output encoding"Where can script injection happen in a Vue app, and how do you render user HTML safely?"
CSP and security headers"Which headers do you ship, and how do you roll out a Content Security Policy in Nuxt?"
Auth, sessions and CSRF"Sessions or JWT? How do you protect against CSRF? How do you authorise?"
Server-side risks"Injection, SSRF, path traversal, uploads, secrets, errors: what do you check in a Nitro route?"
Supply chain and dependencies"What did the 2025 npm attacks teach you, and how is your pipeline protected?"

How it works

The security checklist applied to one Nitro route:

server/api/comments.post.ts
import { z } from 'zod'
import DOMPurify from 'isomorphic-dompurify'

const body = z.object({
  postId: z.string().uuid(),
  html: z.string().max(5_000), // bounded input: DoS via 50 MB comments is a validation problem
})

export default defineEventHandler(async (event) => {
  const { user } = await requireUserSession(event)                   // authentication
  const input = await readValidatedBody(event, body.parse)          // validation at the boundary (400 on failure)
  const post = await useDb().posts.find(input.postId)
  if (!post || !post.allowsComments) throw createError({ status: 404, statusText: 'Not found' }) // object-level authorisation
  await assertRateLimit(event, `comments:${user.id}`, { max: 10, windowSec: 60 }) // abuse control

  const safe = DOMPurify.sanitize(input.html, { ALLOWED_TAGS: ['b', 'i', 'a', 'p', 'code'], ALLOWED_ATTR: ['href'] }) // encode/sanitise on the way in
  const comment = await useDb().comments.create({ postId: post.id, authorId: user.id, html: safe })
  log.info({ event: 'comment.created', userId: user.id, commentId: comment.id }) // audit trail, no content or PII
  return comment
})
Gotcha· Security by framework

"Vue escapes output so we are safe from XSS" and "Nuxt is server-rendered so tokens are safe" are half-truths interviewers probe. Vue escapes interpolation, not v-html, href schemes or style bindings; Nuxt gives you the tools for sessions and validation but every route must use them. Name the mechanism, then the gap.

Exercise

Exercise
  • Draw the trust boundaries of a project you own: every entry point of untrusted data and what validates it. Mark the ones with nothing.
  • Read the OWASP Top 10 (2025) once and, for each category, write one sentence on where it could appear in a Nuxt app.

Be able to say

Be able to say· How do you think about security as a frontend or fullstack engineer?

"In trust boundaries and defaults. Untrusted input enters from URLs, bodies, headers, cookies, uploads, third parties and my own database when it holds user content; each entry validates with a schema and each exit encodes for its context. The browser gives me the same-origin policy, SameSite and HttpOnly cookies, CSP and HTTPS, and I configure them deliberately; it does not give me authorisation or input validation, so every server route authenticates, checks the object it is about to touch, validates and bounds its input, and never leaks stack traces or secrets. Then I treat the process as part of the surface: dependencies updated on a cadence with a release delay, CI with least privilege, security releases applied fast, and logs that would let me notice an incident. The OWASP Top 10, including its 2025 additions on supply chain and error handling, is the checklist I review against."

Pages in this section

  1. 01XSS and output encodingThe three kinds of cross-site scripting, exactly what Vue escapes and what it does not, the sinks to watch — v-html, href and src schemes, style bindings, user templates, JSON in script tags, SVG uploads, postMessage — sanitisation with DOMPurify on both sides, Trusted Types, and how CSP limits the blast radius.
  2. 02CSP and security headersContent Security Policy in practice — directives, nonces versus hashes versus strict-dynamic, report-only rollout, how Nuxt's inline payload and scripts interact with it and what nuxt-security automates — plus HSTS, nosniff, Referrer-Policy, Permissions-Policy, frame-ancestors, the cross-origin isolation headers and subresource integrity.
  3. 03Authentication, sessions and CSRFServer sessions versus stateless sealed cookies versus JWTs and where each breaks, OAuth 2.1 and OIDC with PKCE, passkeys, password hashing and login hardening, CSRF defences from SameSite to origin checks and tokens, and authorisation done on the server for every object.
  4. 04Server-side risks in Nitro routesInjection and parameterised queries, SSRF through user-supplied URLs and image proxies, path traversal, prototype pollution, ReDoS, open redirects, file uploads, secrets and configuration, error handling that does not leak, logging without PII, timing-safe comparisons, webhooks, and denial-of-service by unbounded input.
  5. 05Supply chain and dependency hygieneWhat the 2025 npm attacks changed, OWASP's new supply-chain category, lockfiles and frozen installs, pnpm's release-age delay and blocked install scripts, provenance and trusted publishing, update automation with cooldowns, CI with least privilege, SBOMs, and how to keep Nuxt and its ecosystem patched.

Your notes

Open in Notes

Nothing yet. Notes filed here show up on every page of this section.