Skip to content
RU
← All articles

Prevent XSS Attacks: Escaping, CSP and Trusted Types

In short. Every XSS is user input that reached a place where it is treated as code. The defence is contextual escaping — HTML, attribute, JavaScript and URL contexts each need a different one — because escaping for the wrong context is indistinguishable from not escaping at all.

Cross-Site Scripting (XSS) lets an attacker run arbitrary JavaScript in the context of your domain. Consequences: session-cookie theft, keylogging, phishing, full account takeover. XSS sits under OWASP Top 10 Injection and is still a fixture of every pentest report. This post covers the three flavours of XSS, context-aware escaping, CSP, and Trusted Types as defence-in-depth.

The three flavours of XSS

  • Stored XSS — payload is persisted (comment, profile) and served to every visitor. Worst case.
  • Reflected XSS — payload sits in a URL parameter and is echoed back in the response. Requires a phishing click.
  • DOM-based XSS — payload handled by client-side JS (innerHTML, document.write), never touching the server.

Context-aware escaping

Core rule: the escape function depends on the output context.

<!-- HTML body -->
<div>{{ escaped_html }}</div>       ← &, <, >, ", '

<!-- HTML attribute -->
<img alt="{{ escaped_attr }}">        ← same + quotes

<!-- JS string literal -->
var x = "{{ escaped_js }}";           ← escape \, ', ", newline, unicode

<!-- URL attribute -->
<a href="{{ url_encoded }}">          ← rawurlencode + scheme allowlist

<!-- CSS value -->
color: {{ css_escaped }};              ← only [a-zA-Z0-9#%]

PHP

function e(string $s): string {
  return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

echo '<div>', e($userInput), '</div>';

JavaScript (DOM)

// Dangerous
element.innerHTML = userInput;

// Safe
element.textContent = userInput;

// When you really need HTML — use DOMPurify
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);

React / Vue

React escapes {variable} by default. The only traps: dangerouslySetInnerHTML and dynamic href:

// Bad
<a href={userInput}> ... // javascript: scheme

// Good
const safeHref = userInput.startsWith('http') ? userInput : '#';
<a href={safeHref}>

CSP as the last line

Even perfect escaping cannot save you from a framework zero-day. CSP turns a successful XSS into a blocked script. Minimum:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-RANDOM' 'strict-dynamic'; object-src 'none'; base-uri 'self'

Details in CSP setup.

Trusted Types (Chrome 83+)

Trusted Types force every DOM sink (innerHTML, script.src) to only accept strings that went through a policy function — dramatically reducing DOM XSS surface:

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types default
trustedTypes.createPolicy('default', {
  createHTML: (s) => DOMPurify.sanitize(s),
});
element.innerHTML = 'raw html'; // TypeError without policy

HttpOnly cookies

The HttpOnly flag prevents JS from reading cookies — so even a successful XSS cannot exfiltrate the session. Always combine with Secure and SameSite. See Cookie Security.

Server-side checks

  • WAF (mod_security, Cloudflare) catches basic <script> payloads
  • Whitelist-based input validation (email, URL, integer)
  • Content-Type: application/json on API responses — never text/html for data
  • HTML sanitisation for rich editors: HTMLPurifier (PHP), DOMPurify (JS)

Testing

Canonical test payload: <img src=x onerror="alert(1)">. If an alert fires, you have XSS. Static analysis: Semgrep, Snyk Code, SonarQube. Header hygiene via the Security Scanner.

FAQ

Is PHP's strip_tags enough? No — it's bypassed with <svg>, onerror, HTML entities. Use htmlspecialchars for body and DOMPurify/HTMLPurifier for rich content.

Does HTTPS protect against XSS? No — XSS runs inside JS on the legitimate origin.

Is jQuery .html() dangerous? Yes — it's a wrapper over innerHTML. Use .text().

What about Markdown input? Render to sanitised HTML via marked + DOMPurify; never allow raw HTML passthrough.

Conclusion

Layered defence: context escaping + CSP + Trusted Types + HttpOnly cookies + WAF. Watch CSP reports through enterno monitors and validate headers with the Security Scanner. Related reads: CSP Setup, Cookie Security.

Check your website right now

Check your site's security →
More articles: Security
Security
How to Check a Website for Malware: 4 Layers and a Cleanup
01.04.2026 · 1 104 views
Security
Web Server Security Hardening Checklist: Nginx and Apache
16.03.2026 · 533 views
Security
HSTS and Preload List: Complete Implementation Guide
16.03.2026 · 422 views
Security
How to Check a Website for Fraud: 12 Signs of a Phishing Site
18.07.2026 · 420 views