Skip to Content
Course content

163: Avoiding Cross-Site Scripting in Client Code

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Wait, how can a simple string of text actually run code on my page?

It happens because of how the browser interprets HTML. If you use .innerHTML to push data from a user—say, a username or a bio—directly into the DOM, you're telling the browser: "Take this string and parse it as HTML."

I've seen this happen in production way too often. Imagine you have a user profile page. You fetch the user's bio from an API and do this:

const userBio = "I love coding! ";
document.getElementById('bio-display').innerHTML = userBio;

The browser sees the <img> tag, fails to find the image at source "x", and immediately fires the onerror event. Just like that, the attacker is running arbitrary JavaScript in your user's browser. They aren't just popping up alerts; they could be stealing session tokens or redirecting your users to a phishing site.

Is there a safer way to put text on the screen without risking this?

Absolutely. In 90% of cases, you don't actually need to render HTML; you just want to show text. The gold standard here is .textContent.

When you use .textContent, the browser doesn't try to parse the string. It treats everything as a literal string of characters. If a user tries to inject a <script> tag, the page will literally display the characters "<script>" on the screen rather than executing them.

const userBio = "I love coding! <img src=x onerror=alert('Hacked!')>";
// This is the safe way
document.getElementById('bio-display').textContent = userBio;

I always tell my juniors: treat .innerHTML as a "red flag" property. If you see it in a code review, ask yourself: "Do we really need to render HTML here, or could .textContent do the job?"

What happens if I actually need to allow some HTML, like bold or italics?

This is where things get tricky. Sometimes you're building a CMS or a comment section where you want to allow <b> or <i> tags, but obviously not <script> or <iframe>.

Don't try to write your own Regular Expression to "clean" the string. Trust me on this—attackers are much more creative than your Regex will be. They'll find a way around your filter using encoding or weird attribute combinations.

Instead, use a dedicated sanitization library like DOMPurify. It takes a dirty string and returns a clean one based on a strictly defined whitelist of safe tags and attributes.

import DOMPurify from 'dompurify';

const dirtyHtml = '<img src=x onerror=alert(1)> <b>Hello!</b>';
const cleanHtml = DOMPurify.sanitize(dirtyHtml);

// Now it's safe to use innerHTML because the dangerous parts are stripped out
document.getElementById('comment-body').innerHTML = cleanHtml; 
// Result: <b>Hello!</b> (The img tag is gone)

It's a small dependency, but it saves you from a massive security headache.




📋 Practical Task

Securing the User Comment Feed

You are working on a community forum. There is a bug where users can execute scripts by posting comments. You have been given a piece of vulnerable code that uses .innerHTML to render comments from an array.

Your Task:

  • Create an HTML file with a <div id="comment-list">.
  • Create a JavaScript array of "comments" where at least one comment contains a malicious payload (e.g., <img src=x onerror="alert('XSS!')">).
  • Write a function that loops through the comments and appends them to the #comment-list.
  • Initially, implement it using .innerHTML to confirm the vulnerability (you'll see the alert fire).
  • Then, refactor the code to use .textContent (or document.createElement('p') with .textContent) to ensure that the malicious payload is rendered as harmless text.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.