v157 · miscellaneous · sanitizer api

Add javascriptURLs option to SanitizerConfig and Sanitizer

The Sanitizer API has always removed javascript: URLs from navigation attributes — but only as a hidden side effect of which method you called: setHTML() stripped them, setHTMLUnsafe() kept them, and no configuration could say otherwise. HTML PR 12898 regularises this into an explicit, developer-selectable knob: a javascriptURLs boolean on SanitizerConfig and a setJavascriptURLs(allow) method on Sanitizer. Defaults are unchanged — safe methods still always strip (removeUnsafe() forces the option off), unsafe methods still default to keeping — but an unsafe-path sanitization can now opt in to stripping javascript: URLs.

concepts

  1. javascript: URL playground

    Paste markup with javascript: hrefs and run it through the four real call shapes — setHTML(), setHTML() with javascriptURLs: true (spoiler: removeUnsafe() wins), setHTMLUnsafe() with the new javascriptURLs: false, and the permissive unsafe default. Serialized output, a per-attribute kept/removed diff, and a navigation-disabled rendered preview, all from genuine API calls.

  2. Config inspector

    The option's lifecycle, probed step by step: what new Sanitizer().get() canonicalises javascriptURLs to, the exact boolean setJavascriptURLs() returns (true = changed, false = no-op — twice in a row proves it), and removeUnsafe() forcing the option back to false. Every step prints the real return value or the real exception.

  3. Escape artists

    Edge-case matrix: obfuscated spellings (JaVaScRiPt:, leading whitespace, HTML-entity-encoded, tab-split scheme) crossed with every navigation attribute the spec names — a/area href, form action, input/button formaction, SVG and MathML href. Which cells the sanitizer actually strips, computed live, including the ones it deliberately leaves alone.

  4. Untrusted comment feed

    The product use case: a comment thread that wants links, so href is on the allowlist — and javascript: hrefs still must not survive. Render hostile comments through safe setHTML(), then through setHTMLUnsafe() + javascriptURLs: false (the new unsafe-but-no-script-URLs middle ground), and inspect every link that comes out.

why it shipped

Before this change, whether javascript: URLs were sanitized was a side effect of the method: any "safe" method stripped them, any "unsafe" method did not, and the SanitizerConfig had no say. That made one real configuration impossible to express — an unsafe-path sanitization (needed when you must keep comments, data-* attributes, or other XSS-irrelevant markup the safe path removes) that still strips javascript: navigation URLs. The HTML Standard regularised the behaviour into the config itself: javascriptURLs defaults to the old behaviour (canonicalised to false for safe calls — and removeUnsafe() always forces it to false — and to true for unsafe calls), so nothing breaks, but the knob is now yours. Behaviour when the option is not used remains exactly the same.

the shipped surface

dictionary SanitizerConfig {
  // ...
  boolean comments;
  boolean dataAttributes;
  boolean javascriptURLs;   // NEW — preserve attributes containing javascript: URLs?
};

partial interface Sanitizer {
  boolean setJavascriptURLs(boolean allow);  // NEW — returns true if the config changed
};

// The new expressible configuration: unsafe path, javascript: URLs still stripped.
el.setHTMLUnsafe(untrusted, { sanitizer: { javascriptURLs: false } });

references