Back to Guides
Guide9 October 2026 · Updated 21 October 2026

How to Prevent XSS Attacks in JavaScript - Concrete Steps for Developers

Decloak is an AI-powered web security intelligence platform that scans a site's HTTP/TLS posture, JavaScript, and third-party scripts to produce a report anyone can read. This guide is part of Decloak's library of practical, source-backed security guidance.

In this guide
  1. Key takeaways
  2. What are the most dangerous ways to inject user data into the DOM?
  3. How do I encode output for the right context?
  4. When must I render user - provided HTML, how can I do it safely?
  5. How does Trusted Types add a safety net for dangerous sinks?
  6. What should a strong Content - Security - Policy look like?
  7. Which JavaScript APIs should I avoid completely?
  8. How do modern frameworks help prevent XSS?
  9. Why is server - side validation still important?
  10. How do I keep my XSS defenses up to date?
  11. Quick checklist for JavaScript code
  12. Comparison of safe vs unsafe DOM insertion methods

Key takeaways

What are the most dangerous ways to inject user data into the DOM?

Directly inserting untrusted strings into innerHTML, outerHTML, insertAdjacentHTML or document.write executes any embedded script. Use text - only APIs such as textContent, innerText, setAttribute (with quoted values), or DOM construction methods (createElement, appendChild). These APIs treat the value as plain text, so markup characters are not parsed as code.

// Bad - executes any <script> in userInput
element.innerHTML = userInput;

// Good - renders userInput as harmless text
element.textContent = userInput;

How do I encode output for the right context?

Encoding must match where the data ends up. For HTML body use &lt;, &gt;, &amp;, &quot;, &#x27;. For attributes also quote the value. For JavaScript strings escape backslashes and quotes. Use a templating engine that performs context - aware encoding automatically (Handlebars, EJS, React JSX, etc.).

function encodeHTML(str) {
 return str.replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":"&#x27;"}[c]));
}
const safe = encodeHTML(userInput);
element.innerHTML = safe; // now safe because characters are encoded

When must I render user - provided HTML, how can I do it safely?

If the feature requires rich - text (comments, editor output), sanitize the string first with a well - maintained library such as DOMPurify.

import DOMPurify from 'dompurify';
const cleanHtml = DOMPurify.sanitize(userHtml);
contentDiv.innerHTML = cleanHtml;

The sanitizer removes <script> tags, event - handler attributes and javascript: URLs, preventing execution.

How does Trusted Types add a safety net for dangerous sinks?

Trusted Types let you define a policy that only returns a TrustedHTML object after sanitization. Assigning a raw string to innerHTML without passing through the policy throws a TypeError. Enable the CSP directive require - trusted - types - for 'script' to enforce it.

const policy = trustedTypes.createPolicy('default', {
 createHTML: input => DOMPurify.sanitize(input)
});
// This works
element.innerHTML = policy.createHTML(userHtml);
// This throws
element.innerHTML = userHtml; // TypeError

What should a strong Content - Security - Policy look like?

A CSP header blocks execution of any script that is not explicitly allowed. Use nonces or hashes for the few inline scripts you must keep, block unsafe-inline and unsafe-eval, and restrict script sources to trusted origins.

Content-Security-Policy: script-src 'nonce-<random>' https://cdn.example.com; 
 object-src 'none';
 base-uri 'self';
 require-trusted-types-for 'script';

If malicious markup slips through, the browser will refuse to run the injected code.

Which JavaScript APIs should I avoid completely?

Never pass user - controlled strings to:

How do modern frameworks help prevent XSS?

Frameworks such as React, Angular, and Vue automatically escape data in templates. Only explicit "dangerous" APIs like dangerouslySetInnerHTML (React) or v-html (Vue) bypass this protection. Keep those features disabled unless you have a strong reason and have already sanitized the content.

Why is server - side validation still important?

Client - side checks can be bypassed. Validate and whitelist input on the server (e.g., allow only alphanumeric usernames, enforce length limits, reject HTML tags) so that malicious payloads never reach the browser in the first place.

How do I keep my XSS defenses up to date?

Quick checklist for JavaScript code

  1. Sanitize any HTML you must render → DOMPurify.sanitize(userHtml).
  2. Encode all other data → element.textContent = userInput; or use a safe templating engine.
  3. Never use innerHTML, outerHTML, insertAdjacentHTML, document.write, eval, setTimeout(string), etc., with untrusted data.
  4. Enable Trusted Types (trustedTypes.createPolicy) and add require-trusted-types-for 'script' to CSP.
  5. Set a strict CSP header with nonces/hashes and no unsafe-inline or unsafe-eval.

Comparison of safe vs unsafe DOM insertion methods

MethodHandles untrusted data safely?Typical use case
textContent / innerTextYes - treats value as plain textDisplaying user comments, usernames
setAttribute (quoted)Yes - attribute value is escapedAdding src or href attributes
createElement + appendChildYes - builds nodes without parsing HTMLConstructing dynamic UI components
innerHTMLNo - parses HTML and executes scriptsLegacy code that injects markup
insertAdjacentHTMLNo - same risks as innerHTMLQuick HTML insertion shortcuts
document.writeNo - writes raw markup into the document streamRarely needed in modern apps

By following these concrete steps, you can eliminate the most common XSS vectors in JavaScript applications and rely on layered defenses that protect even if one layer is missed.

Free security scan

See what's actually exposed on your site.

Decloak's free scan runs in about 15 seconds, no account required, and covers:

  • HTTP/TLS security posture
  • JavaScript CVEs
  • Exposed Supabase/Lovable/Base44 misconfigurations
  • AI-written executive summary