How to use innerHTML safely
Last updated: November 20, 2022.
The innerHTML
method is a powerful tool for rendering an ordinary string as HTML to the DOM. This means you can write HTML markup in JavaScript. With template literal syntax, you can even write entire blocks of HTML with dynamically interpolated content!
But this power comes with a security risk: if you do not fully control of the HTML you are rendering to the page (e.g. it is user content), a malicious actor could run a script to which all of your users are exposed.
In this tutorial, we first look at the security vulnerability. Then, we look at steps you can take to keep your users secure.
Table of contents
Why using innerHTML can be unsafe
This risk with using innerHTML is that a third-party can run some JavaScript on your website to which your users are exposed.
To demonstrate let’s take a look at some code examples.
The following code is perfectly safe because the content of the name
and bio
variables is being hardcoded by you. I hope it’s safe to assume you wouldn’t run malicious code on your own website!
const name = `Rick Astley`;
const bio = `You may know me from my popular 1987 hit, "Never Gonna Give You Up"`;
const profile = document.getElementById('user-profile');
const markup = `
<h2>${name}</h2>
<p>${bio}</p>
`;
profile.innerHTML = markup;
The reason the above code is safe is because we are certain about the source (it’s in the code).
But imagine the values for name
and bio
are being retrieved from a backend server. This introduces some uncertainty about the data you are receiving.
Still, if it is your own server and you created the content, you can still be quite confident that the data you are rendering to the page is safe.
But if it is user data or data from an untrusted third-party server, then you risk malicious code being rendered to your page.
For example, imagine the values of name and bio come from user input, and this leads to the following value for bio
:
const name = `Rick Astley`;
const bio = `<img src="https://unsplash.it/200/200" onload="alert('Evil code!')">`;
const profile = document.getElementById('user-profile');
const markup = `
<h2>${name}</h2>
<p>${bio}</p>
`;
profile.innerHTML = markup;
Uh oh, malicious code is now running on your page! And now, every time a user visits, they are exposed to it.
So the bottom line is that if you use innerHTML
with potentially malicious code (especially user input), you are creating a security vulnerability.
Solutions
This scenario is enough to give any webmaster a fright. Especially as in many cases, removing user interaction is unthinkable.
Thankfully, there are some strategies you can adopt to help protect your users.
✅ #1: Use document.createElement()
and textContent
/innerText
When creating HTML to render to the page it is not strictly necessary to use innerHTML
. Instead, we can use other methods available to use: document.createElement()
for creating new elements and textContent
/innerText
for writing text inside them.
In this way, no opportunity for malicious user input exists: the HTML created is elements only and textContent
/innerText
render any text as pure text. Thus, if the user writes some markup, it will not be rendered as HTML.
For example, to create the same markup as earlier, we could write the following code:
const name = `Rick Astley`;
const bio = `<img src="https://unsplash.it/200/200" onload="alert('Evil code!')">`;
const profile = document.getElementById('user-profile');
const h2 = document.createElement('h2');
h2.innerText = name;
const p = document.createElement('p');
p.innerText = bio;
profile.appendChild(h2);
profile.appendChild(p);
This renders the name
and bio
content to the DOM as text and no script is run! The same result would be achieved if textContent
were used.
So the first solution is to avoid using innerHTML
altogether and instead use methods that do not allow user-created content to be rendered as HTML to the page.
✅ #2: Use a HTML sanitizer if innerHTML
is necessary
There may be some situations where you want to render user input as HTML.
In these cases, you should use a HTML sanitizer, such as
In this case, we should use a HTML sanitizer such as DOMpurify.
In the following example, we will import this package into our project by placing a CDN link from cdnjs in the head section of the HTML.
Once imported, you pass any HTML you want to sanitize into the DOMPurify.sanitize()
method. This will attempt to identify any malicious parts of the HTML and return the value of the sanitized HTML.
Let’s apply this to our ongoing example:
// After importing DOMpurify in HTML...
const name = `Rick Astley`;
const bio = `<img src="https://unsplash.it/200/200" onload="alert('Evil code!')">`;
const profile = document.getElementById('user-profile');
const markup = `
<h2>${name}</h2>
<p>${bio}</p>
`;
profile.innerHTML = DOMPurify.sanitize(markup);
console.log(profile);
// <p><img src="https://unsplash.it/200/200"></p>
The sanitizer has performed a minor miracle here: the src
of the image element is kept but the onload
attribute (the problematic one from a security perspective) has been removed!
This method is not 100% effective, no matter which sanitizer you use. But as you can see in this example, it provides at some security in situations where you want a user to be able to write HTML.
Conclusion: Is it still okay to use innerHTML?
Yes! There are plenty of use cases where it is perfectly safe and appropriate to use innerHTML. For example, innerHTML
is very useful and safe when rendering static content and dynamic content you are completely trust.
However, the security risks occur when innerHTML
is used when working with user input or data from an insecure source. In these cases, we recommend the following two steps outlined in this article: using document.createElement()
and textContent
/innerText
when these can achieve the desired outcome. And employing a HTML sanitizer if the use of innerHTML is unavoidable.
Related links
- OpenJavaScript
- DOMPurify on GitHub