Skip to main content Accessibility Feedback

A safer alternative to innerHTML with vanilla JS

The innerHTML property provides a really simple and convenient way to create HTML templates as strings and inject them into the DOM. But, it can leave you exposed to cross-site scripting attacks.

Today, we’re going to look at a convenient way to reduce your risk.

innerHTML is amazingly simple

The innerHTML property provides one of the simplest ways to take a simple template and inject it into the DOM.

var app = document.querySelector('#app');

var template =
	'<h1>Hello, world!</h1>' +
	'<p>Look this happy little squirrel.</p>';

app.innerHTML = template;

And if you want a more React/JSX-like approach, you can use template literals instead.

Note: only works in modern browsers—you’d need to use Babel to transpile it.

var app = document.querySelector('#app');

var template = `
	<h1>Hello, world!</h1>
	<p>Look this happy little squirrel.</p>
`;

app.innerHTML = template;

There’s a big problem, though: this approach leaves you open to cross-site scripting (XSS) attacks.

How cross-site scripting attacks work

The idea behind an XSS attack with innerHTML is that malicious code would get injected into your site and then execute. This is possible because innerHTML renders complete markup and not just text.

There is one built-in safeguard in place, though. Just injecting a script element won’t expose you to attacks, because the section of the DOM you’re injecting into has already been parsed and run.

// This won't execute
var div = document.querySelector('#some-div');
div.innerHTML = '<script>alert("XSS Attack");</script>';

JavaScript that runs at a later time, though, will.

// This WILL run
div.innerHTML = '<script deferred>alert("XSS Attack");</script>';

// This will, too
div.innerHTML = '<img src=x onerror="alert(\'XSS Attack\')">';

Here’s a demo.

With markup you’ve written the danger is minimal. When using content generated by a user or data from a third-party API or other source you don’t control, though, there’s a big risk here.

So what can you do about it?

In the past, I recommended using a sanitizing helper function to strip out HTML for any content you didn’t create.

/*!
 * Sanitize and encode all HTML in a user-submitted string
 * (c) 2018 Chris Ferdinandi, MIT License, https://gomakethings.com
 * @param  {String} str  The user-submitted string
 * @return {String} str  The sanitized string
 */
var sanitizeHTML = function (str) {
	var temp = document.createElement('div');
	temp.textContent = str;
	return temp.innerHTML;
};

// Renders <h1>&lt;img src=x onerror="alert('XSS Attack')"&gt;</h1>
div.innerHTML = '<h1>' + sanitizeHTML('<img src=x onerror="alert(\'XSS Attack\')">') + '</h1>';

This requires a lot of developer discipline, though.

It’s easy to mess up. Did you sanitizing every piece of third-party content? Did you accidentally strip out HTML from valid content? What if the API or user content can contain HTML, but you don’t want the malicious stuff?

Introducing saferInnerHTML()

A few weeks ago I released ReefJS, a lightweight alternative to Vue and React. One of it’s features is that it automatically sanitizes template strings for you.

On a recent project, I needed to inject template strings but didn’t need to create full on stateful components.

So… I ripped out the stuff that handles the sanitization and spun it off into it’s own helper method: saferInnerHTML.js.

Pass in the element you want to inject your string into and the string itself. The saferInnerHTML() method handles the rest.

var app = document.querySelector('#app');

var template = `
	<h1>Hello, world!</h1>
	<p>Look this happy little squirrel.</p>
`;

saferInnerHTML(app, template);

See it in action here.

And here’s an example with an attempted XSS attack.

saferInnerHTML(app, '<img src=x onerror="alert(\'XSS Attack\')">');

Here’s a demo for it.

If you want to append content to an element rather than wipe it out altogether, saferInnerHTML() also accepts a third argument: append.

Set it to true to append your content in the div.

<div id="app">
	<h1>Hi, universe!</h1>
</div>
var app = document.querySelector('#app');
var template = '<p>You look like nice today!</p>';
saferInnerHTML(app, template, true);

And here’s another demo.

On Monday, I’ll walk through how this all works under the hood. Until then, you can download saferInnerHTML.js on GitHub.