Nuxt 3's unified SSR/CSR architecture and Nitro server engine create a layered attack surface. Key risks: environment variables prefixed with NUXT_PUBLIC_ are serialized into the page HTML payload and exposed to all clients; runtimeConfig.public values are embedded in the __NUXT__ window object on every page render; Nitro server routes under /server/api/ have no authentication middleware by default; and SSR composables using useState with non-request-scoped keys share state between concurrent users. This guide covers systematic Nuxt 3 security assessment.
# Nuxt.js — NUXT_PUBLIC_ env variable and runtimeConfig.public exposure
TARGET="https://your-nuxt-app.example.com"
# Nuxt 3's environment variable rules:
# PRIVATE (server-only): DATABASE_URL=postgres://... SECRET_KEY=...
# PUBLIC (client-exposed): NUXT_PUBLIC_API_BASE=https://api.example.com
#
# runtimeConfig in nuxt.config.ts:
# runtimeConfig: {
# apiSecret: process.env.API_SECRET, // server-only
# public: {
# apiBase: process.env.NUXT_PUBLIC_API_BASE, // embedded in HTML
# stripeKey: process.env.NUXT_PUBLIC_STRIPE, // embedded in HTML
# }
# }
# The entire runtimeConfig.public object is serialized into the __NUXT__ window variable.
# Step 1: Extract __NUXT__ payload from any page
curl -s "${TARGET}" 2>/dev/null | \
python3 -c "
import sys, re, json
html = sys.stdin.read()
# Nuxt serializes public runtime config into window.__NUXT__
match = re.search(r'window\.__NUXT__\s*=\s*(\{.+?\})\s*;', html, re.DOTALL)
if match:
try:
data = json.loads(match.group(1))
print('[FOUND] __NUXT__ payload:')
print(json.dumps(data, indent=2)[:1000])
except:
print('[FOUND] __NUXT__ payload (parse error):', match.group(1)[:500])
else:
# Nuxt 3.x uses a different format
match2 = re.search(r')',
r'