text 12 min

Preventing XSS

Security is not one feature you add at the end.

It is a way of thinking about trust.

Every web app receives data from places it does not fully control:

  • form fields
  • URL query parameters
  • API responses
  • browser storage
  • third-party packages
  • copied text
  • uploaded files

Secure JavaScript code treats that data carefully before using it in the DOM, in requests, or in application state.

What Is XSS?

XSS stands for cross-site scripting.

It happens when untrusted content is interpreted as code by the browser.

The most common beginner mistake is inserting user-controlled text as HTML.

js
const comment = new URLSearchParams(location.search).get("comment");

document.querySelector("#preview").innerHTML = comment;

This is dangerous because innerHTML asks the browser to parse the string as HTML.

If the string came from a user, an API, or a URL, your code may accidentally give that string the power to change the page.

Use Text APIs for Text

If you want to display text, use textContent.

js
const comment = new URLSearchParams(location.search).get("comment") ?? "";

document.querySelector("#preview").textContent = comment;

textContent treats the value as text.

Characters like < and > are displayed as characters, not parsed as tags.

Safe DOM Creation

Create elements with DOM APIs when you can.

js
function renderComment(comment) {
  const article = document.createElement("article");

  const author = document.createElement("h3");
  author.textContent = comment.author;

  const body = document.createElement("p");
  body.textContent = comment.body;

  article.append(author, body);
  return article;
}

This pattern separates structure from data.

Your code controls the elements.

User data becomes text inside those elements.

Avoid HTML Templates with Untrusted Data

Template literals are useful, but they do not make HTML safe.

js
function renderUser(user) {
  return `
    <li>
      <strong>${user.name}</strong>
      <span>${user.bio}</span>
    </li>
  `;
}

If user.name or user.bio came from an untrusted source, this can become unsafe when inserted with innerHTML.

Use DOM APIs or a framework that escapes text by default.

When Is innerHTML Acceptable?

Sometimes an app intentionally renders HTML.

Examples:

  • a trusted documentation page
  • content generated by your own markdown renderer
  • a server-rendered template you control

Even then, be careful.

If any part of the HTML includes user input, sanitize it with a trusted sanitizer before rendering.

Do not write your own sanitizer as a beginner project.

HTML parsing has many edge cases.

Escaping vs Sanitizing

Escaping changes special characters so they are treated as text.

For example, < may become &lt;.

Sanitizing removes or limits unsafe HTML while allowing some safe markup.

For example, a sanitizer might allow <strong> but remove event handler attributes.

For plain text display, prefer text APIs instead of manually escaping.

For user-generated rich text, use a well-maintained sanitizer on the server or client.

Event Handler Attributes

Do not build event handlers inside strings.

js
// Avoid this pattern
buttonContainer.innerHTML = `<button onclick="${action}">Run</button>`;

Use addEventListener.

js
const button = document.createElement("button");
button.textContent = "Run";

button.addEventListener("click", () => {
  runSelectedAction();
});

buttonContainer.append(button);

This keeps code as code and data as data.

Defensive Rendering Checklist

Before putting data on the page, ask:

  • Where did this value come from?
  • Am I displaying text or rendering HTML?
  • Am I using textContent when I only need text?
  • Is this value being used in a URL, CSS, or attribute?
  • Does a trusted library already handle escaping for this context?

Different output locations need different safety rules.

Text content, HTML attributes, URLs, CSS, and JavaScript strings are different contexts.

Common Mistakes

Do not assume API data is safe just because it came from your backend.

Do not assume hidden form fields are safe.

Do not put raw user input into innerHTML.

Do not store unsafe HTML in localStorage and render it later.

Do not disable framework escaping unless you fully understand the risk.

Summary

XSS happens when untrusted data becomes executable browser content.

Use textContent and DOM creation APIs for ordinary text.

Avoid innerHTML for untrusted data.

When you must render rich HTML, use a trusted sanitizer and keep the allowed markup narrow.