Low watch CVSS 4.3 chrome Input Validation 🔧 Commit mapped

Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in ReaderMode
DescriptionImproper input validation in ReaderMode
ComponentReaderMode
Sub-componentReader Mode
Bug ClassInput Validation
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker523237735
Fix commit558ee3257125 (chromium/src) +33/-1
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-06-12 (per vendor advisory)
Fix landed2026-07-06 (public git)
Disclosed2026-08-25
Patch gap50 days — fix public in source before the advisory

Background

Reader Mode / DOM Distiller
the feature that extracts article content from a page and re-renders it in a simplified distilled view. content_processing.js post-processes the extracted DOM before it is shown.
`sanitizeLinks`
the routine in content_processing.js (its name comes from the regression tests; the enclosing signature is not in the hunk) that walks every <a> under an element, keeps http/https links (opening them with target="_blank"), and unwraps other links such as javascript: or mailto:, keeping only their text.
SVG `<a xlink:href>`
SVG anchors can carry their target in the XLink-namespaced attribute xlink:href instead of a plain href. element.getAttribute('href') returns null for it, and it can only be read with getAttributeNS('http://www.w3.org/1999/xlink', 'href').
`querySelectorAll('a')`
the type selector matches <a> elements in any namespace, so SVG anchors are already included in allLinks. Only the attribute read missed them.

Root Cause Analysis

sanitizeLinks read each link’s destination with linkElement.getAttribute('href') and wrapped all sanitization in if (href) { ... }. For an SVG <a> whose target is in xlink:href, that call returns null, so the condition was false and the element was skipped. It was neither unwrapped nor normalized, and it kept its javascript: (or other non-http) destination in the distilled output. This violated the invariant that every anchor reaching the Reader Mode view has an http/https destination or has been unwrapped.

The fix makes the read fall back to the namespaced attribute (getAttribute('href') || getAttributeNS(XLINK_NS, 'href')), so an xlink:href-only anchor enters the existing keepLink logic. A javascript: target is now removed, leaving only its text, and an https target is kept with target="_blank", as both new tests assert.

The patch tightens validation by widening what the sanitizer inspects. The keep/strip decision logic itself is unchanged.

Key insight
The sanitizer took “no plain href attribute” to mean “not a link”, but SVG anchors are live links through xlink:href. Reading both attribute forms closes the bypass.

Attack Path

  1. Craft article markup an attacker-controlled page includes an inline <svg><a xlink:href="javascript:..."> element with text inside, in content that the distiller will extract.
  2. User opens Reader Mode DOM Distiller extracts the content and runs sanitizeLinks over it.
  3. Sanitizer skips the anchor getAttribute('href') returns null, the if (href) block does not run, and the SVG anchor and its javascript: target pass through unchanged (the pre-patch behavior the new test exercises).
  4. User clicks the link in the distilled view the surviving javascript: (or other non-http) link is presented as part of the Reader Mode page. Whether script actually runs depends on the distilled page’s CSP and navigation handling, which the diff does not show (inferred).

Impact Assessment

An attacker can carry a link with a disallowed scheme, such as javascript:, through Reader Mode’s link sanitizer into the distilled view, defeating a defense-in-depth filter. User interaction (opening Reader Mode and clicking) is required. The vendor rates it low as improper input validation, and the diff shows only the sanitizer bypass, so script execution in the Reader Mode context is (inferred) and not demonstrated. The diff contains nothing about in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
if
components/dom_distiller/core/javascript/content_processing.js
modified
suite
components/test/data/dom_distiller/content_processing_tester.js
modified

Files Changed

  • components/dom_distiller/core/javascript/content_processing.js
  • components/test/data/dom_distiller/content_processing_tester.js

Audit Directions

  • Namespace and alias blind spots in JS sanitizers
    look for other content_processing.js or DOM Distiller passes, and WebUI or Reader Mode sanitizers, that read href/src/action with getAttribute only. These miss xlink:href, MathML href, and SVG <use>/<image> attributes.
  • Dual-attribute precedence
    the fix uses ||, so when a non-empty href exists, xlink:href is never examined. Check how the keep path rewrites attributes and whether an element with a benign href and a hostile xlink:href (or the reverse, depending on the renderer’s precedence) can still navigate to the unchecked value.
  • Post-sanitization mutation
    SVG animation elements (<set>, <animate attributeName="href">) and other link-like elements (<area>, <form action>, formaction) can change or create navigation targets after a link-only pass, so verify the distiller removes or neutralizes them.
diff --git a/components/dom_distiller/core/javascript/content_processing.js b/components/dom_distiller/core/javascript/content_processing.js
index 2445f16..1b521108 100644
--- a/components/dom_distiller/core/javascript/content_processing.js
+++ b/components/dom_distiller/core/javascript/content_processing.js
@@ -11,7 +11,8 @@
   const allLinks = element.querySelectorAll('a');
 
   allLinks.forEach(linkElement => {
-    const href = linkElement.getAttribute('href');
+    const href = linkElement.getAttribute('href') ||
+        linkElement.getAttributeNS('http://www.w3.org/1999/xlink', 'href');
 
     if (href) {
       let keepLink = false;
diff --git a/components/test/data/dom_distiller/content_processing_tester.js b/components/test/data/dom_distiller/content_processing_tester.js
index 519d5a5..e6ddc5e 100644
--- a/components/test/data/dom_distiller/content_processing_tester.js
+++ b/components/test/data/dom_distiller/content_processing_tester.js
@@ -82,6 +82,37 @@
     assert.equal(links.length, 0);
     assert.equal(testContainer.innerHTML, 'Mailto Link');
   });
+
+  test(
+      'sanitizeLinks should remove javascript SVG links with xlink:href',
+      async function() {
+        const {assert} = await import('./index.js');
+        testContainer.innerHTML = '<svg><a xlink:href="javascript:void(0)">' +
+            '<text>SVG JS Link</text></a></svg>';
+        sanitizeLinks(testContainer);
+        const links = testContainer.querySelectorAll('a');
+        assert.equal(links.length, 0);
+        assert.equal(
+            testContainer.querySelector('svg').innerHTML,
+            '<text>SVG JS Link</text>');
+      });
+
+  test(
+      'sanitizeLinks should keep valid http/https SVG links with xlink:href ' +
+          'and open in new tab',
+      async function() {
+        const {assert} = await import('./index.js');
+        testContainer.innerHTML =
+            '<svg><a xlink:href="https://example.com">' +
+            '<text>SVG HTTPS Link</text></a></svg>';
+        sanitizeLinks(testContainer);
+        const links = testContainer.querySelectorAll('a');
+        assert.equal(links.length, 1);
+        assert.equal(
+            links[0].getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
+            'https://example.com');
+        assert.equal(links[0].target, '_blank');
+      });
 });
 
 suite('ContentProcessing.removeExtraneousElementsFrom', function() {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/test/data/dom_distiller/content_processing_tester.js b/components/test/data/dom_distiller/content_processing_tester.js
index 519d5a5..e6ddc5e 100644
--- a/components/test/data/dom_distiller/content_processing_tester.js
+++ b/components/test/data/dom_distiller/content_processing_tester.js
@@ -82,6 +82,37 @@
     assert.equal(links.length, 0);
     assert.equal(testContainer.innerHTML, 'Mailto Link');
   });
+
+  test(
+      'sanitizeLinks should remove javascript SVG links with xlink:href',
+      async function() {
+        const {assert} = await import('./index.js');
+        testContainer.innerHTML = '<svg><a xlink:href="javascript:void(0)">' +
+            '<text>SVG JS Link</text></a></svg>';
+        sanitizeLinks(testContainer);
+        const links = testContainer.querySelectorAll('a');
+        assert.equal(links.length, 0);
+        assert.equal(
+            testContainer.querySelector('svg').innerHTML,
+            '<text>SVG JS Link</text>');
+      });
+
+  test(
+      'sanitizeLinks should keep valid http/https SVG links with xlink:href ' +
+          'and open in new tab',
+      async function() {
+        const {assert} = await import('./index.js');
+        testContainer.innerHTML =
+            '<svg><a xlink:href="https://example.com">' +
+            '<text>SVG HTTPS Link</text></a></svg>';
+        sanitizeLinks(testContainer);
+        const links = testContainer.querySelectorAll('a');
+        assert.equal(links.length, 1);
+        assert.equal(
+            links[0].getAttributeNS('http://www.w3.org/1999/xlink', 'href'),
+            'https://example.com');
+        assert.equal(links[0].target, '_blank');
+      });
 });
 
 suite('ContentProcessing.removeExtraneousElementsFrom', function() {
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.