Chapter 31 · Security Best Practices
Subchapter 31.7
references/javascript-typescript-vue-web-frontend-security.mdMarkdown31 KBView on GitHub
This document is designed as a security spec that supports:
Also bundled
OpenAIIt is intentionally written as a set of normative requirements (“MUST/SHOULD/MAY”) plus audit rules (what bad patterns look like, how to detect them, and how to fix/mitigate them).
v-html as a shortcut, bypassing backend auth, or “just store the token in localStorage”).When asked to write new Vue code or modify existing code:
v-html / innerHTML, unsafe URL navigation, dynamic script injection, etc.). (Vue.js (opens in a new tab))While working anywhere in a Vue repo (even if the user did not ask for a security scan):
When the user asks to “scan”, “audit”, or “hunt for vulns”:
Recommended audit order:
.env*, hard-coded keys). (vitejs (opens in a new tab))v-html / innerHTML, URL/style injection, DOM APIs. (Vue.js (opens in a new tab))In a Vue app, untrusted input includes (non-exhaustive):
fetch, axios, GraphQL responses, webhooks, third-party SDKs.route.params, route.query, route.hash, and anything derived from window.location.localStorage, sessionStorage, IndexedDB.postMessage inputs.An action is state-changing if it can:
For each issue found, output:
This is the smallest “production baseline” that prevents common Vue/front-end misconfigurations.
v-html, innerHTML) unless content is trusted or strongly sandboxed. (Vue.js (opens in a new tab))Each rule contains: required practice, insecure patterns, detection hints, and remediation.
Severity: High
Required:
vite, npm run dev, HMR) as the production server.vite preview as a production server. (vitejs (opens in a new tab))vite build) and serve the built assets using a production-grade static server/CDN, or a production SSR server if you are doing SSR. (vitejs (opens in a new tab))Insecure patterns:
vite, npm run dev, or vite preview as the production entrypoint.Detection hints:
vite, npm run dev, pnpm dev, yarn dev, vite preview, vue-cli-service serve.CMD, ENTRYPOINT, CI deploy scripts, platform config.Fix:
vite build.dist/ with hardened hosting (CDN/static server) or integrate into your backend server as static assets.Notes:
Severity: Medium (High if production devtools/debug hooks are enabled)
Required:
.prod.js builds in production. (Vue.js (opens in a new tab))Insecure patterns:
Detection hints:
vue.global.js / non-.prod.js variants when using CDN builds.__VUE_PROD_DEVTOOLS__. (Vue.js (opens in a new tab))Fix:
Severity: High (Critical if real credentials are exposed)
Required:
MUST treat all frontend code and configuration as public.
MUST NOT embed secrets in:
.env files committed to repoimport.meta.env.* variables included in the bundleMUST assume any env var that ends up in the client bundle is attacker-readable. (vitejs (opens in a new tab))
Insecure patterns:
VITE_API_KEY=... containing a true secret (not just a public identifier).Detection hints:
VITE_, import.meta.env, .env, .env.production, .env.*.local.API_KEY, SECRET, TOKEN, PRIVATE_KEY, BEGIN, sk-, AKIA, etc.Fix:
Notes:
.env.*.local should be gitignored and that VITE_* vars end up in the client bundle, so they must not contain sensitive info. (vitejs (opens in a new tab))Severity: High
Required:
envPrefix strict and explicit.Insecure patterns:
envPrefix to overly broad values (or '') to “make env vars work”.Detection hints:
vite.config.* for envPrefix.define: { 'process.env': ... } or manual injection into window.__CONFIG__.Fix:
Notes:
Severity: High
Required:
MUST rely on Vue’s automatic escaping for text interpolation and attribute binding where possible. (Vue.js (opens in a new tab))
MUST NOT render user-provided HTML via:
v-htmlinnerHTML in render functions / JSXelement.innerHTML, insertAdjacentHTML)
unless the HTML is trusted or robustly sanitized and the risk is explicitly accepted. (Vue.js (opens in a new tab))Insecure patterns:
<div v-html="userProvidedHtml"></div>h('div', { innerHTML: userProvidedHtml })<div innerHTML={userProvidedHtml}></div>el.innerHTML = untrustedDetection hints:
v-html, innerHTML, insertAdjacentHTML, DOMParser, document.write.Fix:
Notes:
Severity: Critical
Required:
Insecure patterns:
createApp({ template: '<div>' + userProvidedString + '</div>' }).mount(...)Detection hints:
template: where the value is not a static string.@vue/compiler-dom, compile(, “runtime compiler” build selection, dynamic SFC compilation.Fix:
Severity: Medium
Required:
Insecure patterns:
#app, then Vue mounts on #app and compiles/interprets that DOM as a template.Detection hints:
mount('#app') where #app includes server-rendered UGC.Fix:
Severity: High
Required:
href, src, action, window.location, window.open, router navigation to external).javascript: URL execution in bindings like <a :href="userProvidedUrl">. (Vue.js (opens in a new tab))https: and expected hosts; allow mailto:/tel: only if intended).Insecure patterns:
<iframe :src="userProvidedUrl">window.location = route.query.nextwindow.open(userProvidedUrl)Detection hints:
:href=, :src=, window.location, location.href, window.open, router.push( with untrusted input.next, return_to, redirect query params.Fix:
new URL(...), allowlist protocol/host, reject javascript: and other dangerous schemes.Severity: Low
Required:
:style="userProvidedStyles").Insecure patterns:
:style="userProvidedStyles" where styles are attacker-controlled.<style> content (even if Vue blocks some patterns, don’t try to work around it).Detection hints:
:style=" bound to non-constant variables that originate from API/user content.Fix:
Severity: Critical
Required:
onclick, onfocus, etc.).Insecure patterns:
<div :onclick="userProvidedString"><a :onmouseenter="userProvidedString">Detection hints:
:on followed by event attribute names (:onclick, :onload, etc.).setAttribute('on patterns.Fix:
Severity: High
Required:
Insecure patterns:
beforeEach checks user.isAdmin.”Detection hints:
router.beforeEach for role-based gating and see if the backend is also enforcing.meta.requiresAdmin) with no server corroboration.Fix:
Severity: Low
Required:
next, return_to, redirect).http / https protos (such as javascript:)Insecure patterns:
router.push(route.query.next as string)window.location.href = route.query.redirectDetection hints:
route.query.next, route.query.redirect, return_to, continue, callback.Fix:
/ (and reject //host, javascript:, etc.).Notes:
Severity: Low
Required:
localStorage/sessionStorage.Insecure patterns:
localStorage.setItem('token', ...) for long-lived bearer tokens.Detection hints:
localStorage, sessionStorage, indexedDB, persist, pinia-plugin-persistedstate.Fix:
Severity: High (for cookie-authenticated state-changing requests)
NOTE: If the application is not using cookie based authentication (for example if it passes an Authorization header), then CSRF is not a concern
Required:
credentials: 'include' / withCredentials: true) and cookies authenticate the user, MUST include CSRF protections coordinated with the backend (token/header patterns, Origin checks, SameSite cookies as defense-in-depth). (Vue.js (opens in a new tab))mode: 'no-cors' on the frontend.Insecure patterns:
fetch(url, { credentials: 'include', method: 'POST', body: ... }) with no CSRF token/header usage anywhere.Detection hints:
credentials: 'include', withCredentials, xsrf, csrf, X-CSRF-Token, X-XSRF-TOKEN.Fix:
SameSite=Lax/Strict where compatible and verify Origin/Referer where appropriate (backend-driven). (OWASP Cheat Sheet Series (opens in a new tab))Notes:
Severity: Medium
Required:
Insecure patterns:
/?token=..., /#access_token=... used beyond short-lived OAuth handoff.console.log(userSession) that includes tokens/PII.Detection hints:
token= in router parsing, auth callback handlers, and analytics logs.console.log( around auth code.Fix:
Severity: Medium
Required:
Content-Security-Policy) suitable for your Vue app.frame-ancestors and/or X-Frame-Options) unless intentional embedding is required.X-Content-Type-Options: nosniff, plus other headers as appropriate (Referrer-Policy, Permissions-Policy). (OWASP Cheat Sheet Series (opens in a new tab))Insecure patterns:
unsafe-inline/unsafe-eval without strong justification.Detection hints:
Fix:
Severity: Low
Required:
v-html), SHOULD consider enabling Trusted Types to reduce DOM XSS risk. (web.dev (opens in a new tab))Insecure patterns:
innerHTML/v-html without sanitization or CSP hardening.Detection hints:
v-html, innerHTML, insertAdjacentHTML.require-trusted-types-for 'script' usage (if headers are in repo).Fix:
Severity: Low
Required:
<script src="..."> where the URL is user-controlled.Insecure patterns:
const s=document.createElement('script'); s.src = userProvidedUrl; ...Detection hints:
createElement('script'), .src =, appendChild(script).Fix:
Severity: Low
Required:
integrity attribute) with appropriate crossorigin configuration. (MDN Web Docs (opens in a new tab))Insecure patterns:
<script src="https://cdn.example/..."> with no integrity.Detection hints:
index.html and server templates for https:// script/style tags.integrity=.Fix:
Severity: Low
Required:
Insecure patterns:
Detection hints:
package.json, lockfiles, CI install commands.npm audit disabled, “ignore vulnerabilities” scripts.Fix:
Severity: Medium
Required:
Insecure patterns:
<script> blocks without robust escaping/serialization controls.Detection hints:
__INITIAL_STATE__, window.__*STATE__, template concatenation, and SSR render pipelines.Fix:
When actively scanning, use these high-signal patterns:
Dev/preview servers in production:
npm run dev, vite, vite preview, vue-cli-service serve (vitejs (opens in a new tab))Secrets exposure:
.env, .env.production, .env.*.local, VITE_, import.meta.env, hard-coded API_KEY / SECRET (vitejs (opens in a new tab))XSS sinks:
v-html, innerHTML, insertAdjacentHTML, DOMParser, document.write (Vue.js (opens in a new tab))Client-side template injection:
template: concatenation, compile(, runtime compiler usage, mounting on non-sterile DOM (Vue.js (opens in a new tab))URL injection / open redirects:
:href="..." / :src="..." from user datajavascript: occurrencesroute.query.next / redirect / return_to flowing into router.push or window.location (Vue.js (opens in a new tab))Style injection:
:style="userProvidedStyles" or user-driven theme CSS (Vue.js (opens in a new tab))Token storage:
localStorage.setItem('token'...), persisted auth stores, refresh tokens in JS-accessible storageCSRF integration red flags:
credentials: 'include' / withCredentials: true without any CSRF header/token handling (Vue.js (opens in a new tab))Third-party scripts:
createElement('script')), CDN scripts without SRI (MDN Web Docs (opens in a new tab))External links security:
target="_blank" without rel="noopener"/noreferrer (still recommended for legacy and explicitness) (MDN Web Docs (opens in a new tab))Always try to confirm:
Primary Vue documentation:
https://vuejs.org/guide/best-practices/security (Vue.js (opens in a new tab))https://vuejs.org/guide/essentials/template-syntax (Vue.js (opens in a new tab))https://vuejs.org/guide/best-practices/production-deployment (Vue.js (opens in a new tab))https://link.vuejs.org/feature-flags (Vue.js (opens in a new tab))Vite documentation (common Vue tooling):
https://vite.dev/guide/env-and-mode (vitejs (opens in a new tab))vite preview not designed for production) — https://vite.dev/guide/cli (vitejs (opens in a new tab))server.host can listen on public addresses) — https://vite.dev/config/server-options (vitejs (opens in a new tab))OWASP and web platform hardening references:
https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html (Vue.js (opens in a new tab))https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html (OWASP Cheat Sheet Series (opens in a new tab))https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html (OWASP Cheat Sheet Series (opens in a new tab))https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html (OWASP Cheat Sheet Series (opens in a new tab))https://html5sec.org/ (Vue.js (opens in a new tab))Browser/platform references:
rel="noopener" — https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/rel/noopener (MDN Web Docs (opens in a new tab))https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity (MDN Web Docs (opens in a new tab))https://web.dev/trusted-types/ (web.dev (opens in a new tab))This file