DEV Community

Cover image for A Five-Minute UI Feature That Became an XSS Time Bomb
Parth G
Parth G

Posted on

A Five-Minute UI Feature That Became an XSS Time Bomb

The Innocent UI Change (Context)

Small UI Change
   (preview, comments, tooltip)
           ↓
     Frontend Code
           ↓
     Looks Visual Only
           ↓
     Feels Low Risk

Enter fullscreen mode Exit fullscreen mode

You rolled out what seemed like a tiny HTML or UI tweak. Maybe it was a new place to display user comments, a live preview field, or a way to render formatted user input. Nothing dramatic. After all, it was “just UI.”

But that small feature touched one of the most misunderstood layers of web security: client-side scripting.

What XSS Really Is

Attacker Input
      ↓
Injected JavaScript
      ↓
Victim’s Browser
      ↓
Executes as Trusted App
      ↓
Session / Data / Actions Compromised

Enter fullscreen mode Exit fullscreen mode

Cross-Site Scripting (XSS) is a class of security vulnerabilities where attackers inject malicious client-side scripts, usually JavaScript, into a trusted web application. These scripts execute in other users’ browsers, allowing attackers to hijack sessions, steal sensitive data, or perform actions on behalf of users.

XSS runs entirely in the browser, but the damage it causes is very real.

Where XSS Commonly Comes From

Untrusted Input
   (user / URL / storage)
          ↓
Rendered into DOM
   without escaping
          ↓
Browser Parses HTML
          ↓
JavaScript Executes

Enter fullscreen mode Exit fullscreen mode

XSS vulnerabilities occur when an application takes untrusted input and outputs it into HTML or the DOM without proper handling. Even modern frameworks like React, Angular, and Vue only protect you when used correctly.

If untrusted data reaches a dynamic DOM sink such as innerHTML, the browser will happily execute it.

Why This UI Change Felt Safe

User Input
    ↓
Frontend State
    ↓
Framework Rendering
    ↓
Escaped HTML
    ↓
Safe UI

Enter fullscreen mode Exit fullscreen mode

Frontend developers often assume that UI code is cosmetic, or that frameworks automatically protect against all security issues. That assumption is dangerous.

Many “small” UI changes quietly introduce new data flows into the DOM. When those flows aren’t carefully reviewed, XSS vulnerabilities slip in unnoticed.

Where the Vulnerability Slipped In

User Input
    ↓
JavaScript Reads Value
    ↓
Unsafe DOM Write
(innerHTML / v-html / bypass)
    ↓
Browser Executes Script

Enter fullscreen mode Exit fullscreen mode

Most UI-introduced XSS bugs today fall into one category: DOM-based XSS.

What DOM-Based XSS Is

Server Response
   (Clean HTML)
        │
        ▼
 Browser Loads Page
        │
        ▼
 Client-Side JavaScript
 Reads Untrusted Data
        │
        ▼
 Writes into DOM
        │
        ▼
 JavaScript Executes

Enter fullscreen mode Exit fullscreen mode

DOM-based XSS occurs when unsafe JavaScript running in the browser reads untrusted input and writes it into the DOM without sanitization or encoding. The server response may be completely clean. The vulnerability exists entirely on the client side.

A typical flow looks like this:

  • JavaScript reads user input from the URL, form fields, or storage
  • That input is written directly into the DOM
  • The browser interprets it as executable code

No backend exploit required.

Sources and Sinks: The Anatomy of DOM XSS

[ SOURCES ]                    [ SINKS ]
───────────                  ───────────
URL Params   ─────┐
location.hash ────┼──▶ innerHTML
Form Inputs ──────┼──▶ v-html
Local Storage ────┼──▶ dangerouslySetInnerHTML
API Responses ────┘    document.write

Enter fullscreen mode Exit fullscreen mode

DOM-based XSS always involves two components:

Sources: where untrusted data enters (URL parameters, location.hash, form inputs)

Sinks: where data is rendered (innerHTML, eval, document.write, framework escape hatches)

If data flows from a source to a sink without proper handling, you have an exploitable path.

Framework Bypasses Are the Usual Culprits

Framework Default Rendering
(auto-escaping enabled)
           │
           ▼
        Safe Output
           │
           └───────────────┐
                           ▼
                Escape Hatch Used
        (dangerouslySetInnerHTML,
         v-html, bypassSecurityTrust)
                           │
                           ▼
                     No Protection
                           │
                           ▼
                        XSS Risk

Enter fullscreen mode Exit fullscreen mode

Modern frameworks escape content by default, but they all provide escape hatches:

React’s dangerouslySetInnerHTML

Angular’s bypassSecurityTrustHtml

Vue’s v-html

Direct DOM APIs like innerHTML

These APIs are often introduced to “fix rendering issues” or support formatted content. They also disable the framework’s safety net.

Why Frontend Devs Underestimate XSS

“Security Is a Backend Problem”

A common misconception is that backend sanitization makes the frontend safe. That assumption completely fails for DOM-based XSS, which never touches the server.

If unsafe rendering happens in JavaScript, backend defenses are irrelevant.

Framework Safety Creates False Confidence

Frameworks like React, Angular, and Vue do prevent many XSS attacks — until developers bypass their defaults.

Because XSS feels “solved,” developers may not recognize that certain APIs explicitly turn those protections off.

Visual Correctness Masks Real Risk

UI Looks Correct
     ✔
Tests Pass
     ✔
Feature Ships
     ✔
───────────────
Hidden Runtime Risk
     ✖
JavaScript Executes

Enter fullscreen mode Exit fullscreen mode

UI code can look perfect while being dangerously insecure. The feature works. The UI renders. Tests pass.

But under the hood, the browser is executing attacker-controlled code.

That’s what makes XSS so easy to miss.

How to Audit UI Features Safely

Trace Trust Boundaries

For every UI feature that renders user input, ask:

  • Where does this data come from?
  • How does it reach the DOM?
  • Is it escaped, sanitized, or trusted by assumption?

If you can’t confidently answer those questions, the feature needs review.

Prefer Safe Rendering APIs

Use your framework’s default rendering mechanisms whenever possible:

  • React JSX interpolation
  • Angular template bindings
  • Vue mustache interpolation

Avoid manual DOM manipulation and HTML injection unless absolutely necessary.

Test with XSS Payloads

UI features should be tested with basic XSS probes during development and review. This includes testing inputs, URL parameters, and stored content.

If a payload executes, the feature should not ship.

Layer Defenses

No single defense is sufficient. Combine:

  • Framework auto-escaping
  • Context-aware sanitization
  • Content Security Policy (CSP)
  • Code review and documentation

Security works best when layered.

Lessons Every Frontend Dev Should Learn

UI Code Is Security-Critical Code

Anything that renders untrusted data into the DOM is part of your application’s attack surface. UI work is security work.

“Quick Fixes” Create Long-Term Risk

Five-minute features rarely get five-minute threat models. That’s how vulnerabilities survive code review.

Slow down when touching rendering logic.

Document the “Why,” Not Just the “How”

Future developers need to know why a piece of code is safe, not just that it works. Security decisions should be visible and intentional.

Risky vs Safe Code Patterns

React

Risky

<div dangerouslySetInnerHTML={{ __html: userInput }} />

Enter fullscreen mode Exit fullscreen mode

Safe

<div>{userInput}</div>
Enter fullscreen mode Exit fullscreen mode

Safe with required HTML

const clean = DOMPurify.sanitize(userInput);
<div dangerouslySetInnerHTML={{ __html: clean }} />
Enter fullscreen mode Exit fullscreen mode

Angular

Risky

this.sanitizer.bypassSecurityTrustHtml(userInput);

Enter fullscreen mode Exit fullscreen mode

Safe

<div>{{ userInput }}</div>
Enter fullscreen mode Exit fullscreen mode

Vue

Risky

<div v-html="userInput"></div>

Enter fullscreen mode Exit fullscreen mode

Safe

<div>{{ userInput }}</div>
Enter fullscreen mode Exit fullscreen mode

Attack Payload Examples (For Safe, Defensive Testing Only)

Payload Entered
<img src=x onerror=alert(1)>
          ↓
Rendered via innerHTML
          ↓
Browser Parses <img>
          ↓
onerror Fires
          ↓
JavaScript Executes

Enter fullscreen mode Exit fullscreen mode

Use only in environments you own or are authorized to test.

Basic Script Injection

<script>alert(1)</script>

Enter fullscreen mode Exit fullscreen mode

Image Error Handler

<img src=x onerror=alert(1)>

Enter fullscreen mode Exit fullscreen mode

Attribute Injection

"><svg onload=alert(1)>

Enter fullscreen mode Exit fullscreen mode

URL Fragment Test

#<img src=x onerror=alert(1)>

Enter fullscreen mode Exit fullscreen mode

JavaScript URL

<a href="javascript:alert(1)">Click</a>

Enter fullscreen mode Exit fullscreen mode

Comment Breakout

<!--><script>alert(1)</script>

Enter fullscreen mode Exit fullscreen mode

Expected safe behavior: no execution, content escaped or removed.

UI Security Checklist

Before Shipping

☐ Does this feature render user-controlled input?
☐ Is the data flow fully understood?
☐ Are framework defaults being used?

Rendering Safety

☐ No unsafe HTML injection without sanitization
☐ No security bypass APIs without justification
☐ DOM writes reviewed carefully

Testing

☐ UI tested with basic XSS payloads
☐ DOM-based XSS considered
☐ Code reviewed with security in mind

Defense in Depth

☐ Escaping by default
☐ CSP considered
☐ Decisions documented

Mental Model

☐ UI is part of the security surface
☐ Small features can carry big risk
☐ Security choices are deliberate

This Isn’t Hypothetical. This Has Happened Before.

MySpace (2005)

A stored XSS vulnerability turned profile pages into a self-replicating worm.

Over a million users were affected in hours.

All because user content was rendered unsafely.

Twitter (2010)

A single crafted tweet executed JavaScript on hover.

No clicks required.

UI interaction became the exploit vector.

GitHub (multiple incidents)

Even GitHub has publicly disclosed XSS issues caused by:

markdown edge cases

unsafe preview rendering

If teams at that level get this wrong, it’s not about incompetence.

It’s about how easy this is to miss.

Have you ever shipped a UI change that scared you later?

What did you learn from it?

That’s the conversation worth having.

Top comments (1)

Collapse
 
leob profile image
leob

Pretty basic - any time you display "raw" user input in your web UI, be on your guard for XSS ...