The usual openings
- dangerouslySetInnerHTML with content from users, a CMS or an AI model.
- Markdown rendered to HTML without sanitising.
- A link whose href comes from user input, which allows javascript: URLs.
- Libraries or embed widgets that write to innerHTML.
Sanitise before you render
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(userHtml);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;Validate link targets
Accept only the schemes you expect before placing user input in an href.
function safeHref(input: string): string {
try {
const url = new URL(input);
return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : '#';
} catch {
return '#';
}
}Limit the damage
- Keep session tokens in HttpOnly cookies so injected script cannot read them.
- Add a Content-Security-Policy that restricts script sources. Start in report-only mode.
- Avoid storing long-lived secrets in localStorage.
Frequently asked questions
Is my React app immune to XSS?
No. Rendering text is safe, but raw HTML rendering, unvalidated links and third-party scripts can still be exploited.
Does a CSP replace sanitising?
No. A CSP reduces the impact of an injection. Sanitising prevents it. Use both.