Safeguard
Open Source

Securing Your Vue Project with eslint-plugin-vue

eslint-plugin-vue is the official linter for Vue single-file components. It is not a security scanner, but used well it removes whole classes of bugs that turn into vulnerabilities.

Yukti Singhal
Platform Engineer
5 min read

eslint-plugin-vue is the official ESLint plugin for Vue.js, and while it is a code-quality and correctness tool rather than a security scanner, configuring it well eliminates several patterns that commonly become security bugs in Vue applications. If you write Vue single-file components, this plugin is what lets ESLint understand .vue files at all — the <template>, <script>, and <style> blocks that a plain JavaScript parser cannot read.

Setting up Vue ESLint correctly is worth a few minutes because the alternative is silent gaps: without the plugin, ESLint simply skips your template markup, and the template is exactly where cross-site scripting sneaks into a Vue app.

What eslint-plugin-vue is and is not

The plugin ships rule sets that catch template syntax errors, deprecated APIs, reactivity mistakes, and style inconsistencies. It parses SFCs using vue-eslint-parser so rules can reason about directives like v-if, v-for, and v-html.

What it is not: a taint-analysis engine. It does not trace user input from a network response into the DOM. So calling it a "security tool" oversells it. Its security value is indirect but real — it flags the constructs most likely to cause trouble and enforces consistency that makes review easier.

The rule that matters most for security: v-html

The single most dangerous directive in Vue is v-html, because it renders raw HTML into the DOM without escaping. Any user-controlled string passed to it is a stored or reflected XSS waiting to happen.

eslint-plugin-vue ships vue/no-v-html, which flags every use so nothing slips in unreviewed:

// eslint.config.js (flat config)
import pluginVue from 'eslint-plugin-vue';

export default [
  ...pluginVue.configs['flat/recommended'],
  {
    rules: {
      'vue/no-v-html': 'error',
    },
  },
];

Turning this to error does not mean you can never render HTML — it means every exception is deliberate. When you genuinely need to render markup (say, sanitized rich text), you sanitize with a library like DOMPurify first and add an inline disable comment that documents why. That comment becomes a searchable audit trail of every raw-HTML sink in your codebase.

Enabling the recommended config

Most teams under-configure the plugin, using only the base rules. The flat/recommended (or plugin:vue/vue3-recommended in the legacy config format) tier catches far more. It enforces attribute ordering, flags unused directives, and warns on patterns that break reactivity — the kind of subtle bug that leads to stale, incorrect UI state, which in an auth-gated view can mean showing content that should be hidden.

Pair it with @typescript-eslint if you use TypeScript, and with a security-focused plugin such as eslint-plugin-security for the JavaScript logic itself. eslint-plugin-vue handles the Vue-specific surface; it does not replace general JavaScript linting.

Keeping the toolchain itself trustworthy

eslint-plugin-vue is a dev dependency, which means it runs on your developers' machines and in CI with full filesystem access. A compromised release of any dev dependency is a supply-chain risk regardless of how benign the package's purpose is. Linters and formatters are attractive targets precisely because everyone installs them and few people read their source.

Sensible hygiene:

  • Commit your lockfile and pin the plugin version so CI is reproducible.
  • Review the changelog before bumping across major versions; ESLint plugins occasionally change rule defaults in ways that mask real issues.
  • Run dependency scanning across your devDependencies, not just runtime deps. An SCA tool will map installed versions to advisories including in the ESLint plugin tree, and a tool such as Safeguard can flag a risky transitive dependency you never installed on purpose.

I am not aware of a critical exploited CVE in eslint-plugin-vue itself, and its role as a build-time linter limits the blast radius. But dev dependencies deserve the same monitoring as production ones; the 2018 event-stream incident showed that a popular, unglamorous package is a fine place to hide malicious code.

A practical secure baseline

For a new Vue 3 project, a reasonable starting configuration is:

  1. Extend flat/recommended.
  2. Set vue/no-v-html to error.
  3. Add eslint-plugin-security for the script logic.
  4. Wire ESLint into a pre-commit hook and CI so no unlinted code merges.
  5. Scan dependencies (including dev) in the same pipeline.

That combination will not stop every bug, but it removes the cheap, avoidable ones and forces a human decision on the dangerous constructs. For how static linting complements dynamic testing of the running app, see our DAST overview.

FAQ

Does eslint-plugin-vue prevent XSS?

Not by itself. It flags the risky v-html directive via vue/no-v-html, which forces you to review every raw-HTML render, but it does not sanitize input or trace tainted data. You still need to sanitize user content before rendering it as HTML.

Should I use the recommended config or just the base rules?

Use the recommended tier. The base rules catch only essential parsing errors; the recommended set enforces the correctness and reactivity rules that prevent the subtle state bugs which can leak gated content.

Is eslint-plugin-vue safe to install?

As a widely used official plugin it is generally trustworthy, but treat it like any dev dependency: pin the version, review major upgrades, and include it in dependency scanning. Its build-time role limits, but does not eliminate, supply-chain risk.

Can it replace a security scanner?

No. eslint-plugin-vue is a linter for Vue code quality and correctness. Combine it with general JavaScript security linting, dependency scanning, and dynamic testing for real coverage.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.