CVE-2026-16394
Overview
Background
- Sanitizer API
- the DOM
Sanitizerthat strips unsafe markup from HTML before it is inserted into a document via APIs likesetHTMLandparseHTML. - Safe baseline configuration
- the built-in
FOR_EACH_BASELINE_REMOVE_ELEMENTlist of elements the sanitizer always removes regardless of any caller-supplied allow/deny configuration. - `<base>` element
- an HTML element whose
hrefattribute sets the document base URL, retargeting how every relative URL in the document is resolved. - `javascript:` URL
- a URL scheme whose body is executed as script when the URL is navigated or activated.
Root Cause Analysis
The vulnerable path was RemoveJavascriptNavigationURLAttribute in Sanitizer.cpp, which treated the <base> element like <a> and <area> by only stripping its href attribute when that attribute contained a javascript: URL, leaving the <base> element itself in the sanitized output. This violated the safe-baseline invariant that document-scope, navigation-affecting elements must not survive sanitization, because a <base> element is dangerous by its very presence: it can retarget all relative URL resolution in the document, and attribute-level javascript: filtering alone is an incomplete mitigation that can be bypassed.
The fix removes <base> from the per-attribute javascript:-stripping check so it is no longer merely patched at the attribute level, and instead adds ELEMENT(XHTML, xhtml, base) to FOR_EACH_BASELINE_REMOVE_ELEMENT so the entire <base> element is dropped by the baseline configuration. This works because eliminating the whole element removes the entire class of <base>-driven URL-retargeting abuse rather than trying to sanitize one attribute value.
<base>) at the attribute level — stripping only a javascript: href — instead of removing the element outright; the fix reclassifies <base> as a baseline-removed element so no <base> reaches the sanitized DOM.Attack Path
- Craft malicious base
The attacker embeds
<base href="javascript:alert(1)"></base>(or another retargeting<base>) in HTML destined for the Sanitizer API. - Submit to sanitizer
The markup is passed through
setHTMLorparseHTML, which the victim relies on to neutralize unsafe content. - Element survives
RemoveJavascriptNavigationURLAttributeonly attempts to remove thehrefattribute and leaves the<base>element in place in the output. - Base retargets resolution
The surviving
<base>alters how relative URLs in the document resolve, defeating the sanitizer’s safety guarantee and bypassing the intended mitigation.
Impact Assessment
<base> element persist in supposedly sanitized HTML, undermining the safety promise applications depend on when handling untrusted markup. This occurs in the content process, in the web-content context of any page or component that routes untrusted HTML through the Sanitizer API. The precondition is that the target uses the Sanitizer (setHTML/parseHTML) as its defense and accepts attacker-influenced markup.Files Changed
dom/security/sanitizer/Sanitizer.cpptesting/web-platform/meta/sanitizer-api/sanitizer-javascript-url.html.initesting/web-platform/meta/sanitizer-api/sanitizer-removeUnsafe.html.ini
Audit Directions
- Attribute-level mitigation of element-level threatsflag sanitizer or filter logic that neutralizes a dangerous element by editing one attribute while leaving the element in the DOM.
- Baseline removal completenessaudit
FOR_EACH_BASELINE_REMOVE_ELEMENTand equivalent allow/deny lists for document-scope elements (base,meta,link) that alter global resolution or policy and must be removed outright. - URL-retargeting sinksreview any element (
<base>,<form action>,<a href>) whose value redirects navigation or resource resolution, ensuringjavascript:is not the only scheme guarded and that removal, not rewriting, is applied where required.
Patch
diff --git a/dom/security/sanitizer/Sanitizer.cpp b/dom/security/sanitizer/Sanitizer.cpp
index 403bf6fbecc..eed9ed94130 100644
--- a/dom/security/sanitizer/Sanitizer.cpp
+++ b/dom/security/sanitizer/Sanitizer.cpp
@@ -1630,6 +1630,7 @@ bool Sanitizer::SetDataAttributes(bool aAllow) {
// https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration
// The built-in safe baseline configuration
#define FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT) \
+ ELEMENT(XHTML, xhtml, base) \
ELEMENT(XHTML, xhtml, embed) \
ELEMENT(XHTML, xhtml, frame) \
ELEMENT(XHTML, xhtml, iframe) \
@@ -1762,8 +1763,7 @@ static bool RemoveJavascriptNavigationURLAttribute(Element* aElement,
// Step 1. If «[elementName, attrName]» matches an entry in the built-in
// navigating URL attributes list, and if attribute contains a javascript:
// URL, then remove attribute from child.
- if ((aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area,
- nsGkAtoms::base) &&
+ if ((aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
aLocalName == nsGkAtoms::href && aNamespaceID == kNameSpaceID_None) ||
(aElement->IsAnyOfHTMLElements(nsGkAtoms::button, nsGkAtoms::input) &&
aLocalName == nsGkAtoms::formaction &&
diff --git a/testing/web-platform/meta/sanitizer-api/sanitizer-javascript-url.html.ini b/testing/web-platform/meta/sanitizer-api/sanitizer-javascript-url.html.ini
new file mode 100644
index 00000000000..2a43dc3811d
--- /dev/null
+++ b/testing/web-platform/meta/sanitizer-api/sanitizer-javascript-url.html.ini
@@ -0,0 +1,6 @@
+[sanitizer-javascript-url.html]
+ [setHTML testcase built-in-navigating-url-attributes-list/2, "<base href="javascript:alert(1)"></base>"]
+ expected: FAIL
+
+ [parseHTML testcase built-in-navigating-url-attributes-list/2, "<base href="javascript:alert(1)"></base>"]
+ expected: FAIL
diff --git a/testing/web-platform/meta/sanitizer-api/sanitizer-removeUnsafe.html.ini b/testing/web-platform/meta/sanitizer-api/sanitizer-removeUnsafe.html.ini
new file mode 100644
index 00000000000..79b34a636f4
--- /dev/null
+++ b/testing/web-platform/meta/sanitizer-api/sanitizer-removeUnsafe.html.ini
@@ -0,0 +1,3 @@
+[sanitizer-removeUnsafe.html]
+ [removeUnsafe removes the right elements and attributes]
+ expected: FAIL
Regression Test / PoC
diff --git a/testing/web-platform/meta/sanitizer-api/sanitizer-javascript-url.html.ini b/testing/web-platform/meta/sanitizer-api/sanitizer-javascript-url.html.ini new file mode 100644 index 00000000000..2a43dc3811d --- /dev/null +++ b/testing/web-platform/meta/sanitizer-api/sanitizer-javascript-url.html.ini @@ -0,0 +1,6 @@ +[sanitizer-javascript-url.html] + [setHTML testcase built-in-navigating-url-attributes-list/2, "<base href="javascript:alert(1)"></base>"] + expected: FAIL + + [parseHTML testcase built-in-navigating-url-attributes-list/2, "<base href="javascript:alert(1)"></base>"] + expected: FAIL diff --git a/testing/web-platform/meta/sanitizer-api/sanitizer-removeUnsafe.html.ini b/testing/web-platform/meta/sanitizer-api/sanitizer-removeUnsafe.html.ini new file mode 100644 index 00000000000..79b34a636f4 --- /dev/null +++ b/testing/web-platform/meta/sanitizer-api/sanitizer-removeUnsafe.html.ini @@ -0,0 +1,3 @@ +[sanitizer-removeUnsafe.html] + [removeUnsafe removes the right elements and attributes] + expected: FAIL