Safe URL Handling
Validate href/src/navigation targets: block javascript: and data: traps, open redirects, and XSS via URLs.
- security
- url
- xss
- open-redirect
- href
URLs are a premier XSS and phishing vector: javascript: hrefs, unvalidated redirects, and data:text/html embeds. Any time user input becomes location, href, src, or iframe.src, run an allowlist — not a denylist of keywords.
Docs: OWASP XSS Prevention, URL API — MDN, Open redirects.
Dangerous sinks
location.href = userInput;
location.assign(userInput);
a.href = userInput;
window.open(userInput);
iframe.src = userInput;
link.setAttribute('href', userInput);
javascript: and data:
// Payload examples attackers try
'javascript:alert(document.domain)';
' data:text/html,<script>/*…*/</script>';
function safeHttpUrl(raw) {
try {
const u = new URL(raw, window.location.origin);
if (u.protocol !== 'http:' && u.protocol !== 'https:') return null;
return u;
} catch {
return null;
}
}
const u = safeHttpUrl(userInput);
if (u) a.href = u.toString();
For same-app paths only, prefer relative path allowlisting over accepting absolute URLs.
React / framework notes
// Still dangerous if user controls the string
<a href={userHref}>link</a>
Frameworks escape text content; they do not magically sanitize URL protocols in attributes. Validate before bind.
URL parsers beat string checks
// Fragile
if (!userInput.includes('javascript:')) { /* bypass with JaVaScRiPt: or encoding */ }
Use URL parsing and check protocol. Consider normalization and decoding carefully — double encoding tricks exist; prefer allowlists.
open() and tabs
const w = window.open(url, '_blank', 'noopener,noreferrer');
rel="noopener noreferrer" on links:
<a href="https://example.com" target="_blank" rel="noopener noreferrer">External</a>
CSS url() and others
User-controlled CSS url() can be abusive (tracking, in older engines script-like behaviors). Don’t inject raw CSS from users without a sanitizer designed for CSS.
Interview out-loud
“I treat URLs as sinks: allow only http/https via the URL constructor, prefer internal path allowlists for redirects, and never assign untrusted strings to href/location. Frameworks don’t sanitize protocols for free.”
Reviewer prompts
- What is the asset (session, PII, money movement)?
- What is the attacker capability (web, XSS, network, dependency)?
- Which control fails closed if misconfigured?
- Is the server still enforcing authz?
- Any new third party, iframe, or URL sink?
Residual risk note
Browser controls reduce likelihood and impact; they do not eliminate bugs. Prefer defense in depth: safe defaults in code, strict headers, and monitoring (CSP reports, auth anomaly alerts). When product pressure weakens a control, write down the accepted risk and a revisit date.
Markdown and CMS links
Any rich text pipeline that turns [x](url) into anchors must run the same allowlist. Autolinkers are frequent XSS and javascript: sources. Sanitize after markdown render, not only before. Add unit tests with encoded payloads (javascript: style entities) appropriate to your sanitizer’s document context.
Related
Further depth
Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.
Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.
Further reading
Related guides
- Open RedirectsUnsafe redirect targets in login return URLs and how to allowlist paths without breaking product flows.
- Content Security Policy (CSP)Reduce XSS blast radius with CSP — script-src nonces/hashes, strict-dynamic, report-only rollout, and what CSP does not fix.
- Frontend Security Threat ModelThreat-model SPAs: assets, attackers, XSS/CSRF/supply-chain, and where frontend controls actually land.
- JWT Storage PitfallsWhy localStorage JWTs are XSS bait, cookie alternatives, refresh patterns, and SPA session designs that age better.
- postMessage SecuritySecure window.postMessage: targetOrigin, event.origin checks, schema validation, and iframe bridges.