Varol Cagdas Tok

Personal notes and articles.

XSS in Modern JavaScript Frameworks

React, Vue, and Angular provide automatic output encoding by default. XSS vulnerabilities in applications using these frameworks almost always result from explicitly bypassing the built-in protections.


React

React encodes all values interpolated into JSX before rendering them as DOM text. The string <script>alert(1)</script> inserted as {userInput} is rendered as text, not as HTML.

The bypass is dangerouslySetInnerHTML. This prop tells React to set the element's innerHTML directly without encoding:

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

Any attacker-controlled string passed to __html executes as HTML, including inline scripts and event handlers. The name is deliberate: the property is named to make its use visible in code review.

The other vector is javascript: URLs in href and src attributes. React does not block these in older versions. A link rendered with href={userInput} where userInput is javascript:alert(1) executes on click.


Vue

Vue encodes text interpolations ({{ userInput }}) the same way React does. The equivalent bypass is the v-html directive:

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

This sets innerHTML without encoding. The Vue documentation explicitly warns that v-html should never be used with user-supplied content. In practice, it appears in code when developers need to render HTML from a CMS or backend that returns formatted text, and apply it to fields that also accept user input.


Angular

Angular's template engine encodes interpolated values and applies DOM sanitization to bound HTML. The bypass is the DomSanitizer service with bypassSecurityTrustHtml():

this.sanitizer.bypassSecurityTrustHtml(userInput)

Values marked as trusted through this method are inserted without sanitization. Angular's sanitizer does remove some dangerous constructs, but bypassSecurityTrustHtml disables it entirely for that value.


Server-Side Rendering

Frameworks that support SSR (Next.js, Nuxt, Angular Universal) serialize application state into the HTML response. If attacker-controlled data appears in that serialized state, it may be injected into the page before the client-side framework initializes. This is a server-side injection problem with XSS consequences: the payload is in the initial HTML, not in a subsequent DOM operation.

The pattern to watch for is unencoded state serialized into a &lt;script&gt; tag during SSR, particularly in patterns like window.__STATE__ = JSON.stringify(data) where data contains user input. Proper JSON encoding prevents injection, but string concatenation does not.