Vue's template syntax auto-escapes interpolated content by default, the same protection React and Angular provide - most Vue-specific security issues come from the small number of places where that default is deliberately turned off, or from assumptions about where trust boundaries actually sit in a single-page application.
v-html: Vue's Equivalent of dangerouslySetInnerHTML
v-html renders a string as raw HTML instead of escaped text - identical in effect to React's dangerouslySetInnerHTML, covered in depth in our DOM-based XSS guide. Any <script> tag or event handler attribute in the bound string executes.
// Fixed: sanitize before binding
import DOMPurify from 'dompurify';
const safeComment = computed(() => DOMPurify.sanitize(userComment.value));
Grep any Vue codebase for v-html as a first pass - a codebase with zero occurrences has zero risk from this specific pattern, and every occurrence found is worth tracing back to confirm the bound value is either fully trusted or sanitized.
Dynamic Component Names From User Input
<component :is="..."> resolves whatever component name it's bound to at render time. If that name is ever derived from user input rather than a fixed, developer-controlled set of options, it opens a path to rendering components that were never meant to be reachable from that context - not typically full code execution, but a real access-control gap if some registered components expose privileged actions.
// Risky if `tab` comes directly from a query parameter
// Safer: map to an explicit allowlist
const ALLOWED_TABS = { profile: ProfileTab, settings: SettingsTab };
const activeTab = computed(() => ALLOWED_TABS[route.query.tab] ?? ProfileTab);
Client-Side Route Guards Are UX, Not Security
Vue Router's beforeEach navigation guards are excellent for user experience - redirecting an unauthenticated user away from a protected route before it renders - but they run entirely in the browser and can be bypassed by anyone who calls the underlying API directly, without ever going through the Vue app at all. Every protected action still needs server-side authorization; the client-side guard is a convenience layer on top of that, never a substitute for it.
// This protects the UI. It does not protect the API endpoint the UI calls.
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) next('/login');
else next();
});
Build-Time Environment Variables Ship to Every Client
Vite (and Vue CLI before it) expose environment variables prefixed with VITE_ (or VUE_APP_) directly into the client bundle at build time - anything given that prefix is fully readable by any visitor who opens dev tools, because it's compiled into static JavaScript, not fetched securely at runtime.
# .env - this ships to every browser that loads the app
VITE_API_SECRET_KEY=sk_live_... # never do this
# Correct: server-side secrets stay server-side, without the VITE_ prefix,
# and are never referenced from client code at all
API_SECRET_KEY=sk_live_...
If a value needs to be genuinely secret, it has no business in a Vite/Vue CLI env file with the client-exposed prefix - it belongs in a backend service the client calls, not in the bundle itself.
Third-Party Component Supply Chain
A UI component library, a form validation plugin, or an analytics integration installed from npm runs with the same DOM access as your own first-party code - there's no sandboxing between a Vue plugin and the rest of the application. Treat adding a new dependency with UI-rendering responsibility (anything that touches v-html-equivalent rendering internally) with the same scrutiny as writing that code yourself, and keep dependencies patched - a compromised or vulnerable third-party component is functionally equivalent to a vulnerability in your own code.
Checking Your Vue App From the Outside
Route guards, environment variable exposure, and missing security headers are all visible from outside the application, the same way they are for React and Next.js - see our Next.js and React security guide for the shared frontend security fundamentals across all three frameworks. Create a free account to run your first Shieldome scan.