Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Allowlist URL schemes for href/src and navigation sinks
Validate user-controlled URLs against a scheme allowlist (http/https) before binding them to href, src, or location to block javascript:/data: XSS.
Bad example
| 1 | function ProfileLink({ website }: { website: string }) { |
| 2 | // website is whatever the user typed into their profile |
| 3 | return <a href={website}>My site</a>; |
| 4 | } |
| 5 |
|
| 6 | function redirect(next: string) { |
| 7 | // next comes from a query param |
| 8 | window.location.href = next; |
| 9 | } |
Explanation (EN)
React escapes text but does not validate URL schemes. A value of javascript:fetch('/api/keys').then(...) in href executes on click; the same goes for data: documents. Assigning an unvalidated value to window.location enables both DOM-XSS (javascript:) and open redirects to phishing domains.
Objašnjenje (HR)
React escapira tekst, ali ne validira URL sheme. Vrijednost javascript:fetch('/api/keys').then(...) u href-u izvrsava se na klik; isto vrijedi za data: dokumente. Dodjeljivanje nevalidirane vrijednosti window.location omogucava i DOM-XSS (javascript:) i open redirect na phishing domene.
Good example
| 1 | const SAFE_SCHEMES = new Set(['http:', 'https:', 'mailto:', 'tel:']); |
| 2 |
|
| 3 | function toSafeHref(raw: string): string | undefined { |
| 4 | try { |
| 5 | const url = new URL(raw, window.location.origin); |
| 6 | return SAFE_SCHEMES.has(url.protocol) ? url.href : undefined; |
| 7 | } catch { |
| 8 | return undefined; |
| 9 | } |
| 10 | } |
| 11 |
|
| 12 | function ProfileLink({ website }: { website: string }) { |
| 13 | const href = toSafeHref(website); |
| 14 | return href ? <a href={href} rel="noopener noreferrer">My site</a> : <span>My site</span>; |
| 15 | } |
| 16 |
|
| 17 | // For redirects, only allow same-origin or an explicit allowlist of paths. |
| 18 | function redirect(next: string) { |
| 19 | const url = new URL(next, window.location.origin); |
| 20 | window.location.href = url.origin === window.location.origin ? url.href : '/'; |
| 21 | } |
Explanation (EN)
Parsing with the URL constructor and checking url.protocol against an allowlist rejects javascript:/data: and other dangerous schemes before they reach the sink. For redirects, constraining the target to the same origin (or an explicit allowlist) prevents both DOM-XSS and open-redirect phishing.
Objašnjenje (HR)
Parsiranje s URL konstruktorom i provjera url.protocol prema allowlisti odbacuje javascript:/data: i druge opasne sheme prije nego dodu do sinka. Za redirecte, ogranicavanje cilja na isti origin (ili eksplicitnu allowlistu) sprjecava i DOM-XSS i open-redirect phishing.
Notes (EN)
The same scheme check applies to img/iframe/source src, form action, and CSS url(). Covered by OWASP DOM-based XSS Prevention Cheat Sheet.
Bilješke (HR)
Ista provjera sheme vrijedi za img/iframe/source src, form action i CSS url(). Pokriveno OWASP DOM-based XSS Prevention Cheat Sheet-om.
Exceptions / Tradeoffs (EN)
URLs that are fully constructed server-side from trusted data don't need client validation, but validating cheaply is still good defense in depth. Add mailto:/tel: to the allowlist only where those links are actually expected.
Iznimke / Tradeoffi (HR)
URL-ovi koji su u potpunosti izgradeni na serveru iz pouzdanih podataka ne trebaju klijentsku validaciju, ali jeftina validacija je dobra dubinska obrana. Dodaj mailto:/tel: u allowlistu samo gdje su ti linkovi stvarno ocekivani.