Skip to content
← All articles

Prevent XSS Attacks: Escaping, CSP and Trusted Types

Prevent XSS Attacks: Escaping, CSP and Trusted Types

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

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

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 SSL/TLS проверку 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 now →
More articles: SEC
SEC
Clickjacking Prevention: X-Frame-Options vs frame-ancestors
15.04.2026 · 6 views
SEC
SQL Injection Prevention: Prepared Statements and ORM
15.04.2026 · 4 views
SEC
WAF (Web Application Firewall): A Practical Guide
15.04.2026 · 4 views
SEC
CORS: Complete Guide to Access-Control-Allow
15.04.2026 · 4 views