How to Prevent XSS Attacks in JavaScript: 6 Techniques With Code Examples

Cross-site scripting (XSS) remains one of the most exploited vulnerabilities on the web, and JavaScript apps are still the primary attack surface in 2026. If you’re building modern SPAs, Node.js APIs, or hybrid apps, learning how to prevent XSS attacks in JavaScript is not optional, it’s a core engineering skill.

This guide skips the theory and gives you 6 actionable defense techniques you can ship today, each with working code examples. It is argued more carefully on mozilla.org.

What Is an XSS Attack in JavaScript?

An XSS attack happens when an attacker injects malicious JavaScript into your web application, which is then executed in the browser of another user. The result can be session hijacking, credential theft, defacement, or full account takeover.

There are three main types you should recognize:

Type How it works Typical vector
Stored XSS Malicious script saved on the server and served to users Comments, profiles, forum posts
Reflected XSS Script reflected from URL or form back into the page Search fields, error pages
DOM-based XSS Vulnerability lives entirely in client-side JS innerHTML, location.hash, document.write

Now let’s fix them.

javascript security code

1. Never Use innerHTML with Untrusted Data

The single most common source of DOM-based XSS is innerHTML. If you insert user input directly, you hand attackers the keys.

Vulnerable code:

const username = new URLSearchParams(location.search).get('name');
document.getElementById('greeting').innerHTML = 'Hello ' + username;
// URL: ?name=<img src=x onerror=alert(1)>  → executes

Safe alternative using textContent:

const username = new URLSearchParams(location.search).get('name');
document.getElementById('greeting').textContent = 'Hello ' + username;
// The <img> tag is rendered as literal text, not HTML

Rule of thumb:

  • Use textContent when you want text
  • Use setAttribute when you want attributes
  • Use createElement + appendChild for structured DOM
  • Reserve innerHTML for static, developer-controlled strings only

2. Sanitize HTML with DOMPurify When You Really Need Rich Content

Sometimes you actually need to render user-submitted HTML (rich text editors, markdown, emails). Don’t roll your own sanitizer. Use DOMPurify, the de-facto standard.

import DOMPurify from 'dompurify';

const dirty = userSubmittedHtml; // e.g. from a WYSIWYG editor
const clean = DOMPurify.sanitize(dirty, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'ul', 'li', 'br'],
  ALLOWED_ATTR: ['href', 'title']
});

document.getElementById('post').innerHTML = clean;

DOMPurify strips <script> tags, on* event handlers, javascript: URLs, and dozens of other bypass tricks that a homemade regex will miss.

Don’t try to sanitize with regex

Every year a new developer writes something like input.replace(/<script>/gi, '') and every year attackers bypass it with <ScRipT>, <svg onload>, or encoded payloads. Just use a maintained library.

javascript security code

3. Encode Output Based on Context

The same string is dangerous in different ways depending on where you inject it. Encode it for its context:

Context Encoding required
HTML body & < > ” ‘ → HTML entities
HTML attribute HTML entity + always quote the attribute
URL parameter encodeURIComponent()
Inline JavaScript JSON.stringify() then HTML-encode
CSS value Whitelist-based, avoid entirely if possible

Example of a simple HTML encoder:

function escapeHtml(str) {
  return String(str)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

4. Deploy a Strict Content Security Policy (CSP)

A well-configured CSP is your last line of defense. Even if an attacker sneaks a payload through, CSP can block execution.

Send this header from your server:

Content-Security-Policy: 
  default-src 'self'; 
  script-src 'self' 'nonce-r4nd0m123'; 
  object-src 'none'; 
  base-uri 'self'; 
  frame-ancestors 'none';

Then reference scripts with the matching nonce:

<script nonce="r4nd0m123">
  // your legitimate inline script
</script>

Key rules for a strong CSP in 2026:

  1. Avoid 'unsafe-inline' and 'unsafe-eval'
  2. Use nonces or hashes instead of allowlisting domains where possible
  3. Always set object-src 'none'
  4. Add base-uri 'self' to prevent base tag injection
  5. Report violations with report-to before enforcing
javascript security code

5. Enable Trusted Types for DOM XSS Elimination

Trusted Types is now widely supported in Chromium and Firefox browsers, and it’s the most powerful browser-level defense against DOM XSS. It forces every dangerous sink (innerHTML, script.src, etc.) to accept only vetted, policy-approved values.

Enable it via CSP:

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default dompurify;

Then define a policy in your app:

import DOMPurify from 'dompurify';

if (window.trustedTypes && trustedTypes.createPolicy) {
  trustedTypes.createPolicy('default', {
    createHTML: (input) => DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: true })
  });
}

// Now this is automatically sanitized:
element.innerHTML = untrustedInput;

Any code that tries to assign a raw string to innerHTML without going through the policy will throw a TypeError. This is game-changing for large codebases.

6. Use Framework-Native Escaping Correctly

Modern frameworks escape by default, but they all have escape hatches that are XSS landmines.

React

// Safe: automatically escaped
<div>{userInput}</div>

// DANGEROUS: bypasses escaping
<div dangerouslySetInnerHTML={{ __html: userInput }} />

// Safer version if you must render HTML:
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />

Vue

<!-- Safe -->
<div>{{ userInput }}</div>

<!-- DANGEROUS -->
<div v-html="userInput"></div>

Angular

Angular auto-sanitizes bound HTML. Never call bypassSecurityTrustHtml() on untrusted input.

javascript security code

Bonus: Extra Defensive Measures

  • HttpOnly cookies: prevent JavaScript from reading session tokens
  • SameSite=Strict or Lax: reduces impact of stolen sessions
  • Secure flag: cookies transmitted only over HTTPS
  • Subresource Integrity (SRI): verify third-party scripts haven’t been tampered with
  • X-Content-Type-Options: nosniff: prevents MIME confusion attacks
  • Regular dependency audits: run npm audit or Snyk in CI

Quick XSS Prevention Checklist

  1. Replace every innerHTML with textContent where possible
  2. Sanitize all rich HTML input with DOMPurify
  3. Context-aware output encoding on both client and server
  4. Deploy a strict, nonce-based CSP
  5. Enable Trusted Types in supported browsers
  6. Never trust framework escape hatches with user data
  7. Set HttpOnly, Secure, SameSite on all cookies
  8. Audit dependencies regularly

FAQ

What is the best protection against XSS in JavaScript?

There is no single silver bullet. The best protection combines context-aware output encoding, HTML sanitization with a library like DOMPurify, a strict Content Security Policy with nonces, and Trusted Types to eliminate DOM XSS sinks entirely.

Is XSS still relevant in 2026?

Yes. XSS remains in the OWASP Top 10 and continues to be one of the most reported web vulnerabilities. Modern SPAs, third-party widgets, and browser extensions all introduce new attack surfaces every year.

Does React fully protect me from XSS?

No. React escapes text nodes and attributes by default, but dangerouslySetInnerHTML, href with javascript: URLs, and injecting untrusted data into refs or scripts can all still cause XSS. Framework auto-escaping is a helpful default, not a complete solution.

Can a Web Application Firewall (WAF) replace code-level fixes?

No. WAFs catch known payload patterns but are trivially bypassed by encoded or obfuscated attacks. Fix the code first, use WAF as an extra layer. This explainer is clearer than most.

Should I sanitize on input or on output?

Both, but if you have to pick one, always encode on output. The same data can be safe in one context and dangerous in another, so encoding at the point of rendering is what actually stops XSS.

Final Thoughts

Preventing XSS in JavaScript isn’t about memorizing every attack vector, it’s about applying a layered defense. Start by eliminating dangerous DOM sinks, add sanitization where rich content is required, wrap everything in a strict CSP, and enforce Trusted Types where supported. Do these five things well and you’ll have shut down the vast majority of XSS attack paths against your application.

Need help auditing your JavaScript codebase for XSS vulnerabilities? Get in touch with our security team at Coding4 and we’ll run a full assessment.

Leave a Comment

Your email address will not be published. Required fields are marked *