Skip to the solution
EasyFrontend9 min read

What is v-html and what is the risk of using it?

1.0k views

The problem

Question presented to candidate: "Product wants user comments to support bold and links, and someone suggests rendering them with v-html. What does v-html do, what is the danger, and what would you do instead?"

What a strong answer should cover:

  • v-html="value" sets the element's innerHTML, so the string is parsed as real HTML. Interpolation {{ value }} sets text and escapes HTML.
  • With untrusted input it enables cross-site scripting (XSS): event-handler attributes like onerror, javascript: URLs and similar vectors run attacker JavaScript in your origin. (<script> tags inserted through innerHTML do not run, which gives false confidence.)
  • Use it only with trusted content, or sanitize with a maintained allowlist library such as DOMPurify right before rendering; prefer Markdown rendered to safe output, or render structured data with normal templates.
  • Content inserted by v-html is not compiled by Vue (no directives, no components), and scoped styles do not apply to it without :deep().
  • It replaces the element's children (v-html with children is a compile error), and it also renders raw during SSR.
  • Defence in depth: a Content Security Policy and server-side sanitization; the eslint-plugin-vue rule vue/no-v-html flags every use.

Clarifying questions expected:

  • "Where does the HTML come from: our own CMS, a Markdown editor, or arbitrary users?"
  • "Which tags actually need to be supported?"

Code / implementation expected: Yes. Show the unsafe pattern, and a sanitized or alternative version.

  • v-html
  • security

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

VueInterpolation vs v-html with the same untrusted string (harmless payload)
<script setup>
import { ref } from "vue";

// Pretend this came from a user profile, a CMS or an API response.
// The payload is harmless here: it only writes a message into the page.
const comment = ref(
  `Nice post! <img src="does-not-exist.png" onerror="document.querySelector('.alarm').textContent = 'Injected script ran!'">`
);
// Markup you produced yourself, from a trusted source.
const trustedHtml = ref("<strong>Bold</strong> and <em>italic</em>");
</script>

<template>
  <h4>1. Interpolation escapes HTML: safe</h4>
  <p class="safe">{{ comment }}</p>

  <h4>2. v-html inserts it as real HTML: dangerous</h4>
  <p class="unsafe" v-html="comment"></p>

  <h4>3. v-html with trusted markup only</h4>
  <p class="trusted" v-html="trustedHtml"></p>

  <p class="alarm">No script has run.</p>
</template>
VueSanitizing with DOMPurify before v-html (needs the dompurify package)
<script setup>
import { ref, computed } from "vue";
import DOMPurify from "dompurify";

const comment = ref(
  `Nice post! <img src="x.png" onerror="alert(1)"><a href="javascript:alert(2)">link</a><script>alert(3)<\/script><b>kept</b>`
);

// Sanitize at the last moment, right before it reaches v-html.
// Only a small allowlist of formatting tags survives.
const safeComment = computed(() =>
  DOMPurify.sanitize(comment.value, { ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "img"], ALLOWED_ATTR: ["href", "src"] })
);
</script>

<template>
  <p class="out" v-html="safeComment"></p>
</template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 3 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextEasy

What are props in Vue and how do you validate them?

Continue