Suggest an editImprove this articleRefine the answer for “Security and innerHTML”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`innerHTML` does not assign text, it assigns HTML: the browser parses the string and immediately runs everything it finds there, including handler attributes such as `onerror` or `onclick`.** If a comment, a name or any other user supplied field ends up in it, an attacker gets their own JavaScript executed in the context of your domain, which means access to cookies, the auth token and actions performed on behalf of the user. That is XSS (Cross-Site Scripting). For plain text use `textContent`, and if you genuinely need HTML, first run it through a sanitizer such as DOMPurify and repeat the check on the server. ```javascript element.textContent = userInput; // safe: tags become plain text element.innerHTML = DOMPurify.sanitize(html); // only if HTML is truly required ``` **Key point:** `innerHTML` trusts the string as code, so user data must either be escaped via `textContent` or cleaned by a sanitizer.Shown above the full answer for quick recall.Answer (EN)Image**`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 - `innerHTML` does 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 `textContent` or `innerText`, 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 ```javascript 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 `onerror` handler fires and `alert('Hacked!')` runs; - instead of a harmless `alert` an 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: ```javascript element.textContent = userInput; ``` or ```javascript 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](https://github.com/cure53/DOMPurify): ```javascript 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 `innerHTML` just "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 via `onerror`, `onload`, `javascript:`, mixed case or encoding. - **Mixing `innerText` and `innerHTML` in 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.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.