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, Content Security Policy, 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 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
API Rate Limiting: Token Bucket, 429, Retry-After
15.04.2026 · 81 views
SEC
WAF (Web Application Firewall): A Practical Guide
15.04.2026 · 77 views
SEC
CORS: Complete Guide to Access-Control-Allow
15.04.2026 · 64 views
SEC
HTTP to HTTPS Migration: Redirects, Mixed Content, HSTS
15.04.2026 · 62 views