Chapter 1 · Practice 2 of 4
Use text sinks and validate URLs before they reach href or src
Why it matters
OWASP's advice is to refactor away from unsafe sinks: "use textContent or value" instead of innerHTML. Text sinks never parse HTML, so there is nothing to escape.
| Instead of | Use |
|---|---|
el.innerHTML = name | el.textContent = name |
| building markup strings | document.createElement and properties |
dangerouslySetInnerHTML for text | normal JSX children |
setTimeout('fn()', 100) | setTimeout(fn, 100) |
URLs need their own check. Escaping does not stop javascript:alert(1) in an href, because it contains no special HTML characters. OWASP says to allowlist http and https URLs, and notes that React 19 blocking javascript: URLs does not replace application-specific URL validation. Parse the URL with the URL constructor and check its protocol, rather than testing the string with a regular expression.
When the content genuinely needs HTML (a comment with formatting), sanitize it with a maintained library. OWASP recommends DOMPurify, kept up to date, with its output used as-is.
Example
export function safeHref(input: string, base = window.location.origin) {
try {
const url = new URL(input, base);
return url.protocol === 'https:' || url.protocol === 'http:' ? url.href : '#';
} catch {
return '#';
}
}
// React: plain children are escaped, the URL is checked
<a href={safeHref(profile.website)} rel="noopener noreferrer">{profile.name}</a>