3 Security Pitfalls Every React Developer Should Know

Common React vulnerabilities, how to patch them, and how to spot them in a code review

Jasmine Humbert
Draft · 7 min read

React is my favorite library for making interactive interfaces. It is both easy to use and quite secure! However, That doesn’t mean it’s completely safe. It’s easy to get complacent and think “we don’t have to worry about XSS because we use React!”. It’s important to know what React does and doesn’t handle for you.

React vulnerabilities most often happen when you think you’re using the library but aren’t. The following are the most common specific mistakes teams make, how to spot them, and how to fix them.

First, a (Very) Quick Primer on Cross Site Scripting

Cross site scripting (XSS) is a potentially serious client-side vulnerability. It happens whenever an attacker can trick a website into running Javascript in their target’s browser.

Reflected cross site scripting is where a link contains information that gets filled in to the page and then read as code in the browser. Stored cross site scripting is where something uploaded by an attacker gets rendered in a page to a user and run in their browser. Comments and image uploads are common attack vectors for stored XSS.

The Samy Worm exploited an XSS vulnerability in MySpace and was the fastest spreading virus of all time. Vulnerable websites can put users at risk of having login credentials or personal data stolen and it’s a common way to exploit other vulnerabilities. However, malicious scripts are usually used to spam and redirect users to fraudulent sites.

All of the following pitfalls put an application at risk of XSS.

Pitfall #1: Server-Side Rendering Attacker-Controlled Initial State

Sometimes when we render initial state, we dangerously generate a document variable from a JSON string. Vulnerable code looks like this:

<script>window.__STATE__ = ${JSON.stringify({ data })}</script>

This is risky because will blindly turn any data you give it into a string (so long as it is valid JSON) which will be rendered in the page. If has fields that un-trusted users can edit like usernames or bios, they can inject something like this:

{
username: "pwned",
bio: "</script><script>alert('XSS Vulnerability!')</script>"
}

This pattern is common when server side rendering React apps with Redux. It used to be in the official Redux documentation so many tutorials and example boilerplate apps you find on Github still have it.

Don’t believe me? Try it yourself. Google “react server side rendering example app” and try this attack on any of the top results.

How to Spot it in a Code Review

Look for being called with a variable that may have un-trusted data inside a script tag..

Here is the example that used to be in the Redux docs:

Stolen from here

And here’s an instance from an example app I found on Github:

Sometimes this vulnerability is a little harder to spot. The following example would also vulnerable if is not properly escaped.

When server-side-rendering is happening, look at what is being rendered. If user input is not properly escaped and is rendered in the DOM it could be dangerous.

The Fix

We’re going to use the serialize-javascript NPM module to escape the rendered JSON. If you are server side rendering with a non-Node backend you’ll have to find one in your language or write your own.

$ npm install --save serialize-javascript

Next, import the library at the top of your file and wrap the formerly vulnerably variable like this:

<script>window.__STATE__ = ${ serialize( data, { isJSON: true } ) }</script>

For further reading on this patch, refer to this fantastic article by Emelia Smith.

A side note about serializing state:

It seems silly to me to install a whole other NPM module to do something React usually does under the hood. I talked to a few people who know more about Javascript than I do and they all thought it was an ok solution. The internet also came up empty. If there is a better way to do this, the comment section is open and I am all ears.

Pitfall #2: Sneaky Links

An tag can have an that links to another page, an external site, or somewhere on the same page. They can also contain scripts prefixed with . If you didn’t know about this feature of html try it now by copy pasting this into your browser bar:

data:text/html, <a href="javascript: alert('hello from javascript!')" >click me</a>

What that means for web developers is that if you’re setting links based on user input, the URIs can inject a script that will run when another user clicks on it. TK

This pitfall is definitely not unique to React but is one React devs often fall into.

How to Spot it in a Code Review

Can users add links that other users may click on? If so, try adding a ‘link’ like this:

javascript: alert("You are vulnerable to XSS!")

If the alert pops up on the page, you have an XSS vulnerability. Try everywhere these custom links are loaded. Most likely, not every instance will be vulnerable.

How to Fix It

This fix isn’t unique to React, will vary depending on the application and may be better handled on the backend.

One may think to simply remove the prefix. This is an example of blacklisting which isn’t a good strategy for sanitizing data. Hackers have clever ways to bypass filters like this so instead (or in addition) make sure that links use a whitelisted protocol like and escape html entities.

Another strategy that may add additional protection is to make user links open in new tabs. I would recommend against this being the only safeguard however. Opening a link in a new tab is undefined behavior. Most browsers will run the script harmlessly in a blank tab but it isn’t guaranteed and may be possible to get around.

While you’re at it, add a tag to those s to prevent reverse tabnabbing.

Consider using a special UserLink component so a vulnerable tag is less likely to be accidentally added in the future. It’s also worth adding a few tests and/or linting rules to make sure code with this mistake doesn’t make it into production.

For further reading on escaping attacker-controlled props, check out this detailed article by Ron Perris.

Links aren’t the only thing that can be exploited in this way but they’re the most likely to be in React. Any element will be vulnerable to this attack if an attacker can control a URI value. Another possibility, for example, is . For the full list of attributes that can contain URIs, CTRL+F for in this list.

Pitfall #3: Misunderstanding What it Means to Dangerously Set

I greatly appreciate React for putting a security warning right in the name of a method: . Despite this warning, we still frequently see developers dangerously setting bad things. Same goes for .

Take this example that I found on the first page of a google search:

It’s an example of pitfall #1 but with a twist that should have made it extra obvious that something is wrong. To explain themselves, the tutorial states:

We dangerouslySetTheInnerHTML as a method of sanitizing data and preventing XSS attacks

Another example, to prove that this does in fact happen all the time, is this team who found they had a vulnerability where they were adding Markdown to a page using . To mitigate this in the future, they added a linting rule.

How to Spot it in a Code Review

It’s good to do a CTRL+F for and before submitting or merging any pull requests (I do this for console logs too) and/or add a linter warning.

Make sure that any instance of this method loads only trusted data into the page. How do you know if the data is trusted? If it doesn’t come from you, it can be tampered with. This includes data loaded from external APIs and stuff that goes through a Markdown library.

For further reading, look at the documentation. Seriously.

Footnote About Component Spoofing

In 2015, someone figured out that you can spoof components by passing JSON to a component that expects text. I could only find this one instance of component spoofing being reported and the long discussion it sparked about how much React should be responsible for preventing XSS. In the end, the React devs pushed a fix that seems to prevent the vulnerability.

Footnote On SSR

The server-side rendering mistake is so widespread because it was in the Redux documentation and spread from there. They fixed it in 2016, but 3 years later, intro tutorials all over the internet are still teaching it.

So as a challenge to readers, find one example on github and submit a pull request to fix it. Here is an example.

Together, we can squash this bug once and for all!