Why this matters

The bug. Browsers treat javascript:foo() as a URL scheme that *executes JavaScript* on navigation. Setting a.href to attacker-controlled text exposes this without going through innerHTML or eval. Bypasses most string-based XSS detectors. Same risk applies to data: URLs containing HTML.

The fix. Allowlist the scheme: only accept http: and https: (and maybe mailto: if relevant). Anything else gets dropped or replaced with a neutral fragment. React, Vue, and Svelte all do this for you on string-typed href props in newer versions — but raw DOM code (and dangerouslySetInnerHTML) doesn't.

Defense in depth. Set a Content-Security-Policy header that bans inline script. CSP wouldn't catch this specific vector unless you forbid javascript: URLs explicitly via script-src + navigate-to.

Review heuristic

Search the diff for dangerouslySetInnerHTML, innerHTML =, document.write, and v-html. Each of those is an opt-out of the framework's safety. Each one needs a justification in the review and a sanitization step on the input.

External reference: CWE-79: Cross-site Scripting.

OWASP XSS prevention cheat sheet; CWE-79.