
How to Prevent XSS Attacks: Cross-Site Scripting Mitigation for Web Developers
Cross-Site Scripting (XSS) is one of the most prevalent vulnerabilities on the web. It occurs when an application includes untrusted data in a web page without proper validation or escaping, allowing attackers to inject malicious client-side scripts (usually JavaScript) into pages viewed by other users.
Once executed inside a victim's browser, these scripts can access session tokens, hijack cookies, redirect users to phishing sites, or modify page layout content.
In this guide, we will break down the three main types of XSS and outline practical security strategies, including input sanitization, output encoding, and Content Security Policy (CSP).
The Three Types of XSS
1. Stored XSS (Persistent XSS)
Stored XSS is the most damaging type. It occurs when an attacker uploads a malicious script to your database (e.g., inside a blog comment or user profile field). The server saves the script, and whenever other users visit that page, the database serves the malicious code, executing it in their browsers.
2. Reflected XSS (Non-Persistent XSS)
Reflected XSS occurs when a malicious script is reflected off the web server onto the user's browser. Typically, the script is embedded in a query parameter inside a malicious URL. When the victim clicks the link, the server prints the parameter value directly into the HTML response without escaping it.
3. DOM-Based XSS
Unlike Stored and Reflected XSS where the server returns the malicious payload, DOM-based XSS occurs entirely in the client-side JavaScript code. It happens when client-side scripts parse data from unsafe sources (like window.location.hash or query parameters) and write it directly into the DOM using unsafe methods.
Defense 1: Context-Aware Output Encoding
To prevent XSS, you must treat all user data as plain text, never as executable code. You must perform output encoding before writing variables to the page, converting special characters into safe HTML entity equivalents:
&becomes&<becomes<>becomes>"becomes"'becomes'
Modern UI frameworks like React, Astro, and Angular perform automatic HTML escaping by default when you render variables:
// React escapes this input automatically
const userInput = "<script>alert('XSS')</script>";
return <div>{userInput}</div>; // Renders safely as text on the screenHowever, if you explicitly bypass this escaping using unsafe features (such as dangerouslySetInnerHTML in React or raw slot tags in Astro), you introduce XSS vulnerability risks.
Defense 2: Sanitize Rich Text Input
If your application supports rich text editing (such as comment boxes using HTML tags), you cannot use simple output encoding because it will escape legitimate formatting tags like <b> or <i>.
In this scenario, you must pass the HTML input through a sanitization library like DOMPurify before rendering it. DOMPurify strips out unsafe elements (like <script>, onload, or onerror event attributes) while preserving safe structural tags:
First, install the library:
pnpm add dompurify @types/dompurifySanitize the HTML payload:
import DOMPurify from 'dompurify';
const dirtyHTML = '<p>Hello <script>alert("XSS")</script> <b>World</b></p>';
const cleanHTML = DOMPurify.sanitize(dirtyHTML);
// Output: <p>Hello <b>World</b></p>Defense 3: Content Security Policy (CSP)
A Content Security Policy (CSP) is a powerful HTTP header that restricts what resources (scripts, images, stylesheets) the browser is allowed to load and execute on your page.
A strong CSP acts as a secondary defense layer. Even if an attacker successfully injects a malicious script tag, the browser will refuse to run it if the script does not match the policy criteria.
Configure a basic secure CSP header in your server or Nginx config:
Content-Security-Policy: default-src 'self'; script-src 'self' https://trustedscripts.com; object-src 'none';default-src 'self': Restricts all assets to load only from your own domain by default.script-src 'self': Restricts script execution to your domain and trusted domains. It blocks inline scripts (e.g.,<script>alert(1)</script>), preventing Reflected or Stored XSS execution.
Conclusion
Securing your web application from XSS requires a defense-in-depth approach. Leverage automatic HTML escaping in frontend frameworks, use DOMPurify to sanitize rich text input before rendering, set the HttpOnly flag on cookies to prevent session theft, and implement a strict Content Security Policy to stop unauthorized scripts from executing in your users' browsers.