Vue.js Security Audit Skill
A comprehensive guide for identifying and remediating security vulnerabilities in Vue.js applications.
When to Use This Skill
Use this skill when:
- Reviewing Vue.js code for security vulnerabilities
- Auditing frontend applications for XSS risks
- Hardening Vue.js applications against common attacks
- Investigating security issues in Vue projects
- Creating security checklists for Vue development teams
Core Vulnerability Categories
1. XSS Sinks in Vue.js
v-html Directive (CRITICAL)
The v-html directive renders raw HTML without sanitization. Any <script> tags or event handlers in user input execute immediately.
Vulnerable:
<div v-html="htmlContent"></div>
Secure:
<div v-html="sanitizedContent"></div>
<script>
import DOMPurify from 'dompurify';
export default {
computed: {
sanitizedContent() {
return DOMPurify.sanitize(this.htmlContent);
}
}
}
</script>
v-bind with URL Attributes (HIGH)
Binding user strings to URL-bearing attributes (href, src, xlink:href, formaction) allows javascript: URI attacks.
Vulnerable:
<a v-bind:href="userInput">Click me</a>
Secure:
<a :href="safeUrl" target="_blank" rel="noopener noreferrer">Click me</a>
<script>
export default {
computed: {
safeUrl() {
if (!this.userInput) return '#';
const url = new URL(this.userInput, 'https://example.com');
if (!['http:', 'https:'].includes(url.protocol)) return '#';
return url.toString();
}
}
}
</script>
v-on with User-Controlled Handlers (CRITICAL)
v-on compiles its value with new Function. User-controlled handler values enable code execution.
Vulnerable:
<button v-on:click="malicious">Click me</button>
<script>
export default {
data() {
return { malicious: 'alert(1)' }
}
}
</script>
Secure:
<button @click="handleClick">Click me</button>
<script>
export default {
methods: {
handleClick() {
// Safe method reference
}
}
}
</script>
Dynamic Attribute/Event Names (HIGH)
User-supplied names in v-bind:[attr] or v-on:[event] let attackers create arbitrary handlers.
Vulnerable:
<img v-bind:[userAttr]="payload">
<!-- userAttr = 'onerror', payload = 'alert(1)' -->
Secure:
<img :src="safeSrc" :alt="safeAlt">
<script>
export default {
computed: {
safeAttr() {
const allowed = ['src', 'alt', 'title'];
return allowed.includes(this.userAttr) ? this.userAttr : null;
}
}
}
</script>
Dynamic Component (<component :is>) (CRITICAL)
User strings in :is can mount arbitrary components or inline templates.
Vulnerable:
<component :is="userChoice"></component>
Secure:
<component :is="safeComponent"></component>
<script>
export default {
computed: {
safeComponent() {
const allowed = ['button', 'input', 'div', 'MyComponent'];
return allowed.includes(this.userChoice) ? this.userChoice : 'div';
}
}
}
</script>
Untrusted Templates in SSR (CRITICAL - RCE Risk)
During server-side rendering, templates run on your server. Injecting user HTML can escalate XSS to Remote Code Execution.
Vulnerable:
const app = createSSRApp({ template: userProvidedHtml })
Secure:
// Never use user input as templates
const app = createSSRApp({
template: require('./App.vue').template
})
Filters/Render Functions with eval (HIGH)
Legacy filters that call eval or new Function on user data are XSS vectors.
Vulnerable:
Vue.filter('run', code => eval(code))
Secure:
// Replace with computed properties
export default {
computed: {
processedValue() {
// Safe transformation without eval
}
}
}
2. Other Common Vulnerabilities
Prototype Pollution in Plugins (HIGH)
Deep-merge helpers in plugins (e.g., vue-i18n) can allow Object.prototype modification.
Vulnerable:
import merge from 'deepmerge'
merge({}, JSON.parse('{ "__proto__": { "polluted": true } }'))
Secure:
import merge from 'deepmerge'
const safeMerge = (target, source) => {
const cleanSource = JSON.parse(JSON.stringify(source));
delete cleanSource.__proto__;
delete cleanSource.constructor;
delete cleanSource.prototype;
return merge(target, cleanSource);
};
Open Redirects with vue-router (MEDIUM)
Unchecked user URLs in router.push or <router-link> enable phishing.
Vulnerable:
this.$router.push(this.$route.query.next)
Secure:
const safePath = this.$route.query.next;
if (safePath && safePath.startsWith('/') && !safePath.includes('..')) {
this.$router.push(safePath);
} else {
this.$router.push('/');
}
CSRF in Axios/fetch (MEDIUM)
SPAs need server-side CSRF tokens; SameSite cookies alone can't block auto-submitted cross-origin POSTs.
Vulnerable:
axios.post('/api/transfer', data)
Secure:
axios.post('/api/transfer', data, {
headers: { 'X-CSRF-TOKEN': this.csrfToken }
})
Click-jacking (MEDIUM)
Vue apps are frameable without proper headers.
Required Headers:
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none';
Content-Security-Policy Pitfalls (HIGH)
The full Vue build needs unsafe-eval. Use runtime build or pre-compiled templates.
Vulnerable CSP:
Content-Security-Policy: script-src 'unsafe-eval';
Secure CSP:
Content-Security-Policy: default-src 'self'; script-src 'self';
Supply-Chain Attacks (HIGH)
Transitive dependencies can run arbitrary code on dev machines (e.g., node-ipc March 2022).
Secure Practices:
npm ci --ignore-scripts # Safer install
npm audit # Regular audits
npm audit fix # Auto-fix where possible
Hardening Checklist
Use this checklist when auditing Vue.js applications:
| # | Check | Severity | Action |
|---|---|---|---|
| 1 | Sanitize all v-html content with DOMPurify |
CRITICAL | Implement sanitization |
| 2 | Whitelist URL schemes in v-bind:href/src |
HIGH | Add URL validation |
| 3 | Never use user input in v-on handlers |
CRITICAL | Use method references |
| 4 | Whitelist dynamic attribute/event names | HIGH | Add allowlist validation |
| 5 | Whitelist components in <component :is> |
CRITICAL | Add component allowlist |
| 6 | Never use user input as SSR templates | CRITICAL | Use pre-compiled templates |
| 7 | Remove eval/new Function from filters |
HIGH | Replace with computed properties |
| 8 | Sanitize deep-merge inputs | HIGH | Clean proto/constructor |
| 9 | Validate router.push URLs | MEDIUM | Check for safe paths |
| 10 | Implement CSRF tokens | MEDIUM | Add X-CSRF-TOKEN header |
| 11 | Set X-Frame-Options: DENY | MEDIUM | Configure server headers |
| 12 | Set CSP frame-ancestors 'none' | MEDIUM | Configure server headers |
| 13 | Use Vue runtime build | HIGH | Switch from full build |
| 14 | Run npm audit weekly |
HIGH | Schedule automated audits |
| 15 | Pin dependency versions | HIGH | Use lockfiles |
Audit Workflow
Step 1: Static Analysis
- Search for
v-htmlusages - Search for
v-bind:href,v-bind:srcwith user data - Search for
v-on:with dynamic values - Search for
<component :is>with user input - Search for
eval(,new Function(in codebase - Check
package.jsonfor known vulnerable packages
Step 2: Runtime Testing
- Test XSS payloads in all user input fields
- Test
javascript:URIs in URL parameters - Test prototype pollution payloads
- Test CSRF with missing tokens
- Test click-jacking by embedding in iframe
Step 3: Configuration Review
- Check CSP headers
- Check X-Frame-Options headers
- Review vue-router configuration
- Audit npm dependencies
- Verify SSR template sources
Quick Reference: Payload Testing
// XSS payloads to test
const xssPayloads = [
'<script>alert(1)</script>',
'<img src=x
'<svg
'javascript:alert(1)',
'data:text/html,<script>alert(1)</script>',
'<iframe src="javascript:alert(1)">',
];
// Prototype pollution payloads
const protoPayloads = [
JSON.stringify({ '__proto__': { 'polluted': true } }),
JSON.stringify({ 'constructor': { 'prototype': { 'polluted': true } } }),
];
References
- Vue.js Security Best Practices
- Vue XSS Guide - StackHawk
- Vue.js Security - Medium
- DOMPurify Documentation
- OWASP Vue.js Security
Tools to Use
- DOMPurify: HTML sanitization library
- npm audit: Dependency vulnerability scanning
- ESLint with security plugins: Static analysis
- Burp Suite: Runtime testing
- Snyk: Supply chain security
Common Mistakes to Avoid
- ❌ Using
v-htmlwithout sanitization - ❌ Binding user input directly to
href/src - ❌ Using
evalin filters or computed properties - ❌ Trusting
router.queryparameters - ❌ Missing CSRF protection on state-changing endpoints
- ❌ Using
unsafe-evalin CSP - ❌ Not pinning dependency versions
- ❌ Skipping
npm auditin CI/CD