Choose your stack
Fix cross-site scripting in Node.js and Express
Print every field through EJS's escaping tag <%= %>, which turns & < > " and ' into entities, and never through the raw tag <%- %>.
Open the fix card Python · FlaskFix cross-site scripting in Python and Flask
Let Jinja autoescaping do the work: render {{ value }} with no |safe filter and no Markup() around user input, so & < > " and ' become entities in both text and quoted attributes.
Open the fix card Java · Spring BootFix cross-site scripting in Java and Spring Boot
Bind text with th:text and attributes with th:attr or th:title, which HTML-escape & < > " and ' — never th:utext for user data.
Open the fix card C# · ASP.NET CoreFix cross-site scripting in C# and ASP.NET Core
Let Razor's @ encode the output: @Model.Name and @Model.Bio HTML-encode & < > " ' and more, in text and in a quoted attribute.
Open the fix card TypeScript · Next.jsFix XSS in TypeScript and Next.js
Render user text as React children and attributes, which React escapes, and keep it out of dangerouslySetInnerHTML; if a field must carry HTML, sanitize it with DOMPurify first.
Open the fix card Python · DjangoFix XSS in Python and Django
Let Django's autoescaping work: render {{ value }} without |safe, mark_safe() or {% autoescape off %}, so < > ' " and & become entities in text and quoted attributes.
Open the fix card Go · net/httpFix cross-site scripting in Go and net/http
Render HTML with html/template, never text/template: it escapes each value for where it lands: text, attribute or URL.
Open the fix card Ruby · RailsFix XSS in Ruby on Rails
Let ERB escape: <%= @name %> and tag helpers such as image_tag(..., alt: @name) escape & < > " and ', unless the value is html_safe.
Open the fix card PHP · LaravelFix cross-site scripting in PHP and Laravel
Print user data with Blade's {{ $value }}, which runs htmlspecialchars() with ENT_QUOTES, so < > & " and ' become entities in text and quoted attributes.
Open the fix cardStacks without a card
- Python and FastAPI: no card yet. The nearest is Fix cross-site scripting in Python and Flask, in the same language.
How it happens
- The template reaches for the engine's raw output hatch, which prints the value with no encoding: EJS <%- %>, Jinja |safe or Markup(), Thymeleaf th:utext, Razor @Html.Raw. A stored or reflected value then carries <script>, an <img onerror=...> tag or a closing tag that breaks out of the surrounding element.
- The value sits in a quoted attribute and the quote is not encoded, so an input such as " onmouseover="... closes the attribute and adds an event handler. An unquoted attribute is worse: a space is enough to add one.
- The value is a URL placed in href or src. HTML-escaping does not help here, because javascript:alert(1) contains no HTML metacharacters, so the escaped string is still a working javascript: URL that runs when the link is clicked.
- A hand-rolled defence is trusted instead of the engine. Escaping only < and > leaves quotes live for attribute injection; a regex that strips <script> misses <img onerror>, <svg onload> and every other event-handler vector; a javascript: blocklist misses mixed case, embedded tabs or newlines, and other dangerous schemes such as data: and vbscript:.
Fix principles
- Render every value through the template engine's default, context-aware escaping (EJS <%= %>, Jinja {{ }} with autoescape, Thymeleaf th:text/th:attr, Razor @). Treat the raw hatch as off-limits for anything a user can influence.
- Always wrap attribute values in quotes so a value cannot grow into a second attribute, and let the engine encode the quote characters.
- A URL is its own context: before putting it in href or src, parse it the way a browser does and allow only known-safe schemes (http, https, mailto) or a relative URL, and replace anything else. Escaping is not a substitute.
- For rich text that must keep some HTML, run it through a maintained, allowlist-based HTML sanitizer (DOMPurify, nh3/Ammonia, OWASP Java HTML Sanitizer or jsoup, Ganss.Xss/HtmlSanitizer) and never a regex. React's dangerouslySetInnerHTML and the equivalents in other frameworks need the same treatment.
- Add a Content-Security-Policy as defence in depth so a missed sink is less likely to execute, but do not let it replace output encoding.
- Keep framework debug pages off in production: they echo input and internals that help an attacker tune a payload.
How the cards prove it
- Run each card's tests in three modes: the fixed implementation must pass, and the vulnerable and the plausible-but-wrong implementations must fail. run-all.mjs does this for every card.
- The regression tests render real payloads (a <script> body, an <img onerror>, a quote that tries to break out of an attribute, a javascript: URL and one with an embedded tab) and parse the result with an HTML5 parser, asserting no script element, no on* handler attribute and no non-http(s) link survives (the Next.js card accepts the throwing javascript: URL React 19 substitutes for any javascript: href, and adds a JSON-LD breakout test). Functional tests confirm that text with <, &, quotes and non-ASCII characters still displays literally.
- Static analysis is uneven. Semgrep CE has registry rules for the EJS and Jinja raw escape hatches but none for Thymeleaf th:utext or a bare Razor @Html.Raw. CodeQL models the EJS <%- %> and Razor @Html.Raw sinks but does not read Jinja or Thymeleaf template contents, so a |safe filter or th:utext is invisible to it. CodeQL was not executed for these cards, except the Go card, where neither of its Go XSS queries followed the value through the card’s interface; each card’s scannerRules records what was actually run and what it found.
References
- OWASP Cross Site Scripting Prevention Cheat Sheet
- OWASP DOM based XSS Prevention Cheat Sheet
- OWASP Top 10:2025 A05 Injection
- CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
- CAPEC-63: Cross-Site Scripting (XSS)
- WHATWG URL Standard: URL parsing (scheme, tab and newline removal)
- MDN: Content-Security-Policy
Attack patterns:
Practice in the browser
- XSS lab: Render a model’s answer as text instead of active markup · Foundation, JavaScript
- HTML sanitization lab: Configure a sanitizer for user comments · Practitioner, JavaScript
Guides on this weakness
Prevent XSS at the output boundary
Choose text rendering, context-aware encoding, or deliberate HTML sanitization according to how the browser will interpret the value.
Configure CORS and CSP around the browser capabilities you need
Control who can read API responses and which resources a page may execute without confusing either control with authorization.
Render rich text without granting it application privileges
Choose text rendering or a restricted HTML policy, then test the final DOM after every transformation.
Keep generated output out of executable contexts
Choose safe rendering and narrow operations so model text cannot become browser code, database syntax or an unrestricted command.
Prevent DOM XSS, postMessage abuse and client-side injection
Trace attacker-controlled values from the URL, messages and storage to the browser APIs that parse HTML or run code, then close those sinks with safe APIs, a sanitizer, Trusted Types and strict message checks.
Apply CISA Secure by Design: principles, pledge goals and evidence
What CISA's Secure by Design guidance and pledge ask of software manufacturers as of October 2026, and how to turn each of the three principles, the seven pledge goals and the Product Security Bad Practices into engineering actions, metrics and evidence.
Worked threat model: an LLM agent with tools
A complete worked threat model for an LLM agent with tools: the data-flow diagram, the threats from prompt injection, excessive agency, data exfiltration, confused-deputy and memory poisoning, and the mitigations, mapped to the OWASP Top 10 for LLM Applications 2026 and the OWASP Top 10 for Agentic Applications 2026.