Security and innerHTML
innerHTML is unsafe with user data because the browser interprets the assigned string as HTML code and executes it. Along with the markup, inline scripts and handler attributes run too, and that is a direct path to an XSS (Cross-Site Scripting) vulnerability.
Theory
TL;DR
innerHTMLdoes not "insert text", it parses the string as HTML and builds DOM nodes from it.- Tags and handler attributes (
onerror,onclick,onload) fire, so a user string effectively becomes executable code. - This is XSS: the attacker's code runs in the context of your site, with your cookies and your session.
- For text use
textContentorinnerText, they escape HTML and turn tags into plain characters. - If you truly need HTML, clean it with a sanitizer (DOMPurify) and validate the data on the server as well.
Quick example
const comment = '<img src=x onerror="alert(`Hacked!`)">';
// Unsafe: the browser parses the tag and runs onerror
document.querySelector('#comments').innerHTML = comment;
// Safe: the string stays plain text
document.querySelector('#comments').textContent = comment;What actually happens
When you assign something to innerHTML, the browser interprets the string as HTML code and executes everything inside it: <script> tags, event attributes (onerror, onclick) and even inline JS.
For the example above the result is:
- an image appears on the page and fails to load;
- the
onerrorhandler fires andalert('Hacked!')runs; - instead of a harmless
alertan attacker could send your user's cookies to their own server.
The problem is not the <img> tag specifically, it is that the string reached the HTML parser at all. There are dozens of payload shapes: <svg onload=...>, <iframe src="javascript:...">, <body onpageshow=...> and so on. Filtering them by hand with a blacklist is hopeless.
What XSS is
XSS (Cross-Site Scripting) is an attack where someone injects JavaScript into your page and the browser executes it in the context of your site. The browser does not distinguish "your" script from "their" script: both have the same privileges.
That gives the attacker the ability to:
- steal cookies, including the auth token;
- replace the content of the page;
- add fake forms that harvest data;
- perform actions on behalf of the user, for example place an order or send a message.
XSS is not only reflected (a value from the URL lands on the page immediately), it can also be stored: the malicious string is saved to the database together with the comment and runs every time anyone else opens the page. Stored XSS is the most dangerous variant.
How to protect yourself
1. Never insert user input through innerHTML.
If you need to show text only, use:
element.textContent = userInput;or
element.innerText = userInput;They escape HTML, turning tags into ordinary text. Creating nodes with document.createTextNode() and setting attributes through setAttribute() is equally safe.
2. If you do need HTML, apply a sanitizer.
When markup really has to be rendered (for example it comes from a moderator or a trusted source), pass the text through an HTML cleaning library such as DOMPurify:
import DOMPurify from 'dompurify';
const safeHTML = DOMPurify.sanitize(userInput);
element.innerHTML = safeHTML;A sanitizer works from an allowlist of tags and attributes, so it strips <script>, any on* handlers and javascript: links.
3. Validate and escape on the server too.
Filter input, especially in forms and comments. Client side checks protect nothing: a request can be sent around your interface. Additionally, enable a Content Security Policy, which removes inline scripts as a class of problem.
Summary table
| Problem | Cause | Safe solution |
|---|---|---|
innerHTML executes code | The inserted HTML is parsed and run by the browser | Use textContent |
A user can inject <script> or onerror | The browser trusts the contents of innerHTML | Use DOMPurify or server side cleaning |
| XSS gives full control over the page | The attack runs in the context of your domain | Validate and escape all user data |
Common mistakes
- Assuming
innerHTMLjust "shows text". It builds DOM, and any tag in the string becomes a real element. - Defending with a blacklist: stripping the substring
<script>with a regular expression. That is trivially bypassed viaonerror,onload,javascript:, mixed case or encoding. - Mixing
innerTextandinnerHTMLin the same codebase. One forgotten property in a template opens a hole in the whole application. - Relying on client side validation only. Data can arrive straight at the API bypassing the form, so cleaning is needed on the server too.
- Trusting "your own" data. A username, a file name, a query parameter value or a third party API response is still untrusted input.
- Sanitizing after assignment. The string must be cleaned before it reaches
innerHTML, otherwise the code has already run.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.