0Pricing
HTML Academy · Lesson

XSS via innerHTML and How to Prevent It

Understand cross-site scripting and safe alternatives to innerHTML.

XSS via innerHTML and How to Prevent It is a free HTML Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the HTML Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Core Vulnerability

Setting el.innerHTML = userInput parses the string as HTML. If userInput contains <script> tags or event-handler attributes (onclick, onerror), the browser interprets them — executing attacker-controlled code on the page.

Why It Is Common

Any code that interpolates user data into HTML — server templates, client-side rendering — risks XSS if the data is not escaped. Single-page apps that build innerHTML from API responses are especially exposed when those responses include user content.

A Concrete Example

A username "Bob<img src=x onerror=alert(1)>" set via div.innerHTML = `Welcome, ${user}` executes alert(1) when the img fails to load. The same payload as plain text (via textContent) is harmless — the browser sees only characters, not tags.

// VULNERABLE
div.innerHTML = `Hi, ${user.name}`;

// SAFE
div.textContent = `Hi, ${user.name}`;

Use textContent for Plain Text

textContent sets text only — HTML special characters appear as literal characters, never as tags. This is the right tool 90% of the time. Switch to innerHTML only when you actually need to render markup, not text.

Use DOM Methods for Structure

To create elements with user data, build them with createElement and textContent: const li = document.createElement("li"); li.textContent = name; ul.appendChild(li);. The result is structurally identical to innerHTML but XSS-safe by construction.

When You Must Use innerHTML

For server-generated trusted HTML (your own template output, sanitized rich-text from a trusted editor), innerHTML is fine. Never apply it directly to data from untrusted sources without sanitization.

Sanitize Rich Text with DOMPurify

If users submit rich text (a comment editor, markdown rendering), sanitize before setting innerHTML: el.innerHTML = DOMPurify.sanitize(richHtml). DOMPurify strips dangerous tags and attributes while keeping safe formatting like <b>, <em>, <a>.

insertAdjacentHTML Has Same Risk

el.insertAdjacentHTML("beforeend", html) parses HTML strings — same XSS surface as innerHTML. Same rules apply: never feed user data directly; sanitize or use DOM methods. Same goes for document.write, though no one should use that anymore.

Framework Defaults

React, Vue, Svelte, Angular all escape interpolated text by default — {name} is safe. They expose escape hatches (dangerouslySetInnerHTML in React, v-html in Vue) which carry the same XSS risk; use sparingly with sanitization.

Attribute Injection

Even attribute values can be vectors: <a href={url}> with url="javascript:alert(1)" executes when clicked. Validate URLs scheme-by-scheme (allow only http:, https:, mailto:, tel:) before placing user input in href, src, or other URL-bearing attributes.

Trusted Types Policy

Modern browsers support Trusted Types: configure CSP with require-trusted-types-for 'script' and innerHTML refuses raw strings — only policy-wrapped values pass. This makes XSS impossible by API rather than by discipline.

Defense in Depth

No single layer is enough. Combine: input validation on the server, output escaping on rendering, CSP to block injected scripts, Trusted Types to refuse raw strings, and security review for any innerHTML usage. Layered defense survives a bug in any one layer.

Knowledge Check

Why is el.textContent = userInput safe from XSS while el.innerHTML = userInput is dangerous?

Summary

innerHTML on user input is the classic XSS vector. Use textContent for text, createElement+textContent for structure, DOMPurify when rich HTML is required. Validate URL schemes for attribute values. Layer CSP and Trusted Types to neutralize bugs that slip through code review. Modern frameworks escape by default — keep their unsafe escape hatches rare and reviewed.

Frequently asked questions

Is the “XSS via innerHTML and How to Prevent It” lesson free?

Yes — the full text of “XSS via innerHTML and How to Prevent It” is free to read here on the web, and the HTML Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the HTML Academy course, upgrade to CoddyKit PRO.

What will I learn in “XSS via innerHTML and How to Prevent It”?

Understand cross-site scripting and safe alternatives to innerHTML. You practise HTML Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start HTML Academy?

No prior experience is required. HTML Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “XSS via innerHTML and How to Prevent It” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this HTML Academy lesson?

Yes. Every HTML Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Content Security Policy meta http-equiv
  2. XSS via innerHTML and How to Prevent It
  3. iframe Sandboxing and Permissions Policy
  4. HTTPS and Subresource Integrity
← Back to HTML Academy