Medium chrome Info Disclosure 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Paint
DescriptionInformation leak in Paint
ComponentPaint
Sub-componentHtml-in-canvas
Bug ClassInfo Disclosure
Tracker553135213
Fix commit91b61e4bed84 (chromium/src) +90/-10
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/core/paint/svg_mask_painter.cc
modified
promise_test
third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-visited.tentative.https.sub.html
modified

Files Changed

  • third_party/blink/renderer/core/paint/svg_mask_painter.cc
  • third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
  • third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-visited.tentative.https.sub.html
  • third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-nested-filter.tentative.https.sub.html
From 91b61e4bed84d126f55fd267ab0c489238a6c3e4 Mon Sep 17 00:00:00 2001
From: Philip Rogers <pdr@chromium.org>
Date: Mon, 14 Sep 2026 12:25:13 -0700
Subject: [PATCH] [html-in-canvas] Disallow SVG masks that are outside a canvas subtree

As with SVG patterns, we disallow the use of masks defined outside of a
canvas subtree, where privacy protections are not available.

Fixed: 553135213
Change-Id: Id4fa486444b0896e574dbf51d65579bb56b86e9b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8398235
Reviewed-by: Stephen Chenney <schenney@chromium.org>
Commit-Queue: Philip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1697429}
---
diff --git a/third_party/blink/renderer/core/paint/svg_mask_painter.cc b/third_party/blink/renderer/core/paint/svg_mask_painter.cc
index 62a4206..a93e4da2 100644
--- a/third_party/blink/renderer/core/paint/svg_mask_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_mask_painter.cc
@@ -283,6 +283,10 @@
   if (!masker) {
     return;
   }
+  if ((paint_flags & PaintFlag::kPrivacyPreserving) &&
+      !masker->GetElement()->IsInCanvasSubtree()) {
+    return;
+  }
   const AffineTransform content_transformation =
       MaskToContentTransform(*masker, reference_box, zoom);
   SubtreeContentTransformScope content_transform_scope(content_transformation);
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
index 5ae47f1..27f10b7 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
@@ -28,9 +28,6 @@
           <feFuncB type="linear" slope="0" intercept="1"/>
         </feComponentTransfer>
       </filter>
-      <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
-        <text x="0" y="50" filter="url(#filter-same)">x</text>
-      </mask>
       <mask id="mask-cross" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
         <text x="0" y="50" filter="url(#filter-cross)">x</text>
       </mask>
@@ -39,6 +36,11 @@
 
   <canvas id="canvas" width="100" height="200" layoutsubtree>
     <svg id="child" width="100" height="200">
+      <defs>
+        <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
+          <text x="0" y="50" filter="url(#filter-same)">x</text>
+        </mask>
+      </defs>
       <svg x="0" y="0" width="100" height="100">
         <rect width="100" height="100" fill="rgb(30, 130, 230)" mask="url(#mask-same)"/>
       </svg>
@@ -61,7 +63,7 @@
         const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
 
         let pixel = _getPixelFromImageData(imgData, 50, 50);
-        assert_array_equals(pixel, [30, 130, 230, 255], "Same origin filter image in external mask should be drawn");
+        assert_array_equals(pixel, [30, 130, 230, 255], "Same origin filter image in internal mask should be drawn");
 
         pixel = _getPixelFromImageData(imgData, 50, 150);
         assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin filter image in external mask should not be drawn");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-nested-filter.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-nested-filter.tentative.https.sub.html
index efaf94f..ed561ee 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-nested-filter.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-nested-filter.tentative.https.sub.html
@@ -28,11 +28,6 @@
           <feFuncB type="linear" slope="0" intercept="1"/>
         </feComponentTransfer>
       </filter>
-      <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
-        <g filter="url(#filter-same)">
-          <rect width="100" height="100" fill="black" filter="url(#filter-same)"/>
-        </g>
-      </mask>
       <mask id="mask-cross" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
         <g filter="url(#filter-cross)">
           <rect width="100" height="100" fill="black" filter="url(#filter-cross)"/>
@@ -53,6 +48,13 @@
 
   <canvas id="canvas" width="100" height="400" layoutsubtree>
     <svg id="child" width="100" height="400">
+      <defs>
+        <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
+          <g filter="url(#filter-same)">
+            <rect width="100" height="100" fill="black" filter="url(#filter-same)"/>
+          </g>
+        </mask>
+      </defs>
       <svg x="0" y="0" width="100" height="100">
         <rect width="100" height="100" fill="rgb(20, 120, 220)" mask="url(#mask-same)"/>
       </svg>
@@ -81,7 +83,7 @@
         const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
 
         let pixel = _getPixelFromImageData(imgData, 50, 50);
-        assert_array_equals(pixel, [20, 120, 220, 255], "Nested same origin filter images in external mask should be drawn");
+        assert_array_equals(pixel, [20, 120, 220, 255], "Nested same origin filter images in internal mask should be drawn");
 
         pixel = _getPixelFromImageData(imgData, 50, 150);
         assert_array_equals(pixel, [0, 0, 255, 255], "Nested cross origin filter images in external mask should not be drawn");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-visited.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-visited.tentative.https.sub.html
new file mode 100644
index 0000000..1eb0896f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-visited.tentative.https.sub.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>drawElementImage does not reveal visited links in external SVG masks</title>
+  <link rel="help" href="https://github.com/WICG/html-in-canvas">
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='/html/canvas/resources/canvas-tests.js'></script>
+  <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+  <style>
+    /* In mask-same, link is white so mask luminance is 1 (alpha 255), allowing the element to draw. */
+    #mask-same a:link    { color: #ffffff; }
+    #mask-same a:visited { color: #ffffff; }
+
+    /* In mask-cross, unvisited is black (luma 0 -> alpha 0), visited is white (luma 1 -> alpha 255).
+       Privacy-preserving rendering disallows external masks, preventing the element from drawing. */
+    #mask-cross a:link    { color: #000000; }
+    #mask-cross a:visited { color: #ffffff; }
+  </style>
+</head>
+
+<body>
+  <svg width="0" height="0">
+    <defs>
+      <mask id="mask-cross" maskContentUnits="objectBoundingBox">
+        <a href="">
+          <rect width="1" height="1" fill="currentColor"/>
+        </a>
+      </mask>
+    </defs>
+  </svg>
+
+  <canvas id="canvas" width="100" height="200" layoutsubtree>
+    <svg id="child" width="100" height="200">
+      <defs>
+        <mask id="mask-same" maskContentUnits="objectBoundingBox">
+          <a href="">
+            <rect width="1" height="1" fill="currentColor"/>
+          </a>
+        </mask>
+      </defs>
+      <svg x="0" y="0" width="100" height="100">
+        <rect width="100" height="100" fill="rgb(30, 130, 230)" mask="url(#mask-same)"/>
+      </svg>
+      <svg x="0" y="100" width="100" height="100">
+        <rect width="100" height="100" fill="rgb(40, 140, 240)" mask="url(#mask-cross)"/>
+      </svg>
+    </svg>
+  </canvas>
+
+  <script>
+    window.onload = () => {
+      promise_test(async function(t) {
+        await waitForCanvasPaint(canvas);
+        var ctx = canvas.getContext('2d');
+        ctx.fillStyle = 'blue';
+        ctx.fillRect(0, 0, 100, 200);
+        ctx.drawElementImage(document.getElementById('child'), 0, 0);
+
+        // Fetch all pixel data once to avoid multiple slow readbacks.
+        const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+        let pixel = _getPixelFromImageData(imgData, 50, 50);
+        assert_array_equals(pixel, [30, 130, 230, 255], "Same origin internal mask should be drawn");
+
+        pixel = _getPixelFromImageData(imgData, 50, 150);
+        assert_array_equals(pixel, [0, 0, 255, 255], "External mask should not be drawn");
+      });
+    }
+  </script>
+</body>
+</html>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
index 5ae47f1..27f10b7 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
@@ -28,9 +28,6 @@
           <feFuncB type="linear" slope="0" intercept="1"/>
         </feComponentTransfer>
       </filter>
-      <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
-        <text x="0" y="50" filter="url(#filter-same)">x</text>
-      </mask>
       <mask id="mask-cross" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
         <text x="0" y="50" filter="url(#filter-cross)">x</text>
       </mask>
@@ -39,6 +36,11 @@
 
   <canvas id="canvas" width="100" height="200" layoutsubtree>
     <svg id="child" width="100" height="200">
+      <defs>
+        <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
+          <text x="0" y="50" filter="url(#filter-same)">x</text>
+        </mask>
+      </defs>
       <svg x="0" y="0" width="100" height="100">
         <rect width="100" height="100" fill="rgb(30, 130, 230)" mask="url(#mask-same)"/>
       </svg>
@@ -61,7 +63,7 @@
         const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
 
         let pixel = _getPixelFromImageData(imgData, 50, 50);
-        assert_array_equals(pixel, [30, 130, 230, 255], "Same origin filter image in external mask should be drawn");
+        assert_array_equals(pixel, [30, 130, 230, 255], "Same origin filter image in internal mask should be drawn");
 
         pixel = _getPixelFromImageData(imgData, 50, 150);
         assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin filter image in external mask should not be drawn");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-nested-filter.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-nested-filter.tentative.https.sub.html
index efaf94f..ed561ee 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-nested-filter.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-nested-filter.tentative.https.sub.html
@@ -28,11 +28,6 @@
           <feFuncB type="linear" slope="0" intercept="1"/>
         </feComponentTransfer>
       </filter>
-      <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
-        <g filter="url(#filter-same)">
-          <rect width="100" height="100" fill="black" filter="url(#filter-same)"/>
-        </g>
-      </mask>
       <mask id="mask-cross" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
         <g filter="url(#filter-cross)">
           <rect width="100" height="100" fill="black" filter="url(#filter-cross)"/>
@@ -53,6 +48,13 @@
 
   <canvas id="canvas" width="100" height="400" layoutsubtree>
     <svg id="child" width="100" height="400">
+      <defs>
+        <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
+          <g filter="url(#filter-same)">
+            <rect width="100" height="100" fill="black" filter="url(#filter-same)"/>
+          </g>
+        </mask>
+      </defs>
       <svg x="0" y="0" width="100" height="100">
         <rect width="100" height="100" fill="rgb(20, 120, 220)" mask="url(#mask-same)"/>
       </svg>
@@ -81,7 +83,7 @@
         const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
 
         let pixel = _getPixelFromImageData(imgData, 50, 50);
-        assert_array_equals(pixel, [20, 120, 220, 255], "Nested same origin filter images in external mask should be drawn");
+        assert_array_equals(pixel, [20, 120, 220, 255], "Nested same origin filter images in internal mask should be drawn");
 
         pixel = _getPixelFromImageData(imgData, 50, 150);
         assert_array_equals(pixel, [0, 0, 255, 255], "Nested cross origin filter images in external mask should not be drawn");
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-visited.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-visited.tentative.https.sub.html
new file mode 100644
index 0000000..1eb0896f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-visited.tentative.https.sub.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>drawElementImage does not reveal visited links in external SVG masks</title>
+  <link rel="help" href="https://github.com/WICG/html-in-canvas">
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='/html/canvas/resources/canvas-tests.js'></script>
+  <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+  <style>
+    /* In mask-same, link is white so mask luminance is 1 (alpha 255), allowing the element to draw. */
+    #mask-same a:link    { color: #ffffff; }
+    #mask-same a:visited { color: #ffffff; }
+
+    /* In mask-cross, unvisited is black (luma 0 -> alpha 0), visited is white (luma 1 -> alpha 255).
+       Privacy-preserving rendering disallows external masks, preventing the element from drawing. */
+    #mask-cross a:link    { color: #000000; }
+    #mask-cross a:visited { color: #ffffff; }
+  </style>
+</head>
+
+<body>
+  <svg width="0" height="0">
+    <defs>
+      <mask id="mask-cross" maskContentUnits="objectBoundingBox">
+        <a href="">
+          <rect width="1" height="1" fill="currentColor"/>
+        </a>
+      </mask>
+    </defs>
+  </svg>
+
+  <canvas id="canvas" width="100" height="200" layoutsubtree>
+    <svg id="child" width="100" height="200">
+      <defs>
+        <mask id="mask-same" maskContentUnits="objectBoundingBox">
+          <a href="">
+            <rect width="1" height="1" fill="currentColor"/>
+          </a>
+        </mask>
+      </defs>
+      <svg x="0" y="0" width="100" height="100">
+        <rect width="100" height="100" fill="rgb(30, 130, 230)" mask="url(#mask-same)"/>
+      </svg>
+      <svg x="0" y="100" width="100" height="100">
+        <rect width="100" height="100" fill="rgb(40, 140, 240)" mask="url(#mask-cross)"/>
+      </svg>
+    </svg>
+  </canvas>
+
+  <script>
+    window.onload = () => {
+      promise_test(async function(t) {
+        await waitForCanvasPaint(canvas);
+        var ctx = canvas.getContext('2d');
+        ctx.fillStyle = 'blue';
+        ctx.fillRect(0, 0, 100, 200);
+        ctx.drawElementImage(document.getElementById('child'), 0, 0);
+
+        // Fetch all pixel data once to avoid multiple slow readbacks.
+        const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+        let pixel = _getPixelFromImageData(imgData, 50, 50);
+        assert_array_equals(pixel, [30, 130, 230, 255], "Same origin internal mask should be drawn");
+
+        pixel = _getPixelFromImageData(imgData, 50, 150);
+        assert_array_equals(pixel, [0, 0, 255, 255], "External mask should not be drawn");
+      });
+    }
+  </script>
+</body>
+</html>
Loading diff…

Original Bug Report

reported by vm...@google.com

Visited link history leak via SVG mask luminance in HTML-in-Canvas

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: The HTML-in-Canvas feature (CanvasDrawElement) enforces privacy-preserving rendering for elements drawn into <canvas layoutsubtree>, but visited-link suppression is only applied style-time to elements that reside inside the canvas subtree. When an element inside the canvas references an external SVG <mask id="..."> located outside the canvas subtree, the mask’s visited styles are resolved and rendered without an IsInCanvasSubtree check. Under the default luminance masking mode, visited link colors are translated into alpha values, potentially allowing web content to read the user’s visited link history via drawElementImage() and getImageData() without tainting the canvas.

Affected files:

  • third_party/blink/renderer/core/paint/svg_mask_painter.cc
  • third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc
  • third_party/blink/renderer/core/css/resolver/style_resolver_state.cc

Estimated timestamp from git blame: 2026-04-29

Summary

When painting descendants of <canvas layoutsubtree> with the HTMLInCanvas origin trial feature (CanvasDrawElement), Blink applies PaintFlag::kPrivacyPreserving to prevent privacy leaks (third_party/blink/renderer/core/paint/paint_layer_painter.cc:633). However, the only visited-link suppression in this path occurs at style-resolution time and is strictly gated on GetElement().IsInCanvasSubtree() (third_party/blink/renderer/core/css/resolver/style_resolver_state.cc:172-175).

When a canvas child references an out-of-subtree SVG <mask id="...">, SVGMaskPainter::ResolveElementReference (third_party/blink/renderer/core/paint/svg_mask_painter.cc:49-70) resolves and paints the mask without checking PaintFlag::kPrivacyPreserving or IsInCanvasSubtree(). Because default SVG masks use luminance masking (EMaskType::kLuminance), visited link colors (e.g., white vs. black) are converted via cc::ColorFilter::MakeLuma() into alpha transparency values on the canvas child. When the child is drawn into the canvas via drawElementImage() (third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc:1407-1500), the canvas remains origin-clean, potentially allowing script to read the visited state via getImageData().

Root Cause

  1. Style-time gate bypass: StyleResolverState::InsideLink() forces visited links to unvisited only if GetElement().IsInCanvasSubtree() is true (style_resolver_state.cc:172-175). Mask resources defined outside the <canvas layoutsubtree> retain EInsideLink::kInsideVisitedLink.
  2. Missing membership gate in mask resolution: Unlike SVG paint servers (which are explicitly gated in third_party/blink/renderer/core/paint/svg_object_painter.cc:34-37 via if (paint_flags & PaintFlag::kPrivacyPreserving && !uri_resource->GetElement()->IsInCanvasSubtree()) return false;), SVGMaskPainter::ResolveElementReference (svg_mask_painter.cc:49-70) and SVGMaskPainter::PaintSVGMaskLayer (svg_mask_painter.cc:273-307) perform no membership or privacy check.
  3. Luma-to-alpha conversion: PaintSVGMaskLayer applies cc::ColorFilter::MakeLuma() when masker->StyleRef().MaskType() == EMaskType::kLuminance (svg_mask_painter.cc:294-297), translating mask RGB into alpha.
  4. Origin-clean readback: BaseRenderingContext2D::DrawElementInternal (base_rendering_context_2d.cc:1407-1500) replays the recorded paint ops without marking the canvas origin as tainted, allowing getImageData() to read the resulting pixel values.

Potential Trigger Steps

(Note: These are potential steps deduced from static code analysis; our tooling agent does not execute code.)

  1. Serve a page with an HTMLInCanvas origin trial token.
  2. In HTML, construct a canvas with layoutsubtree containing an SVG child referencing an external mask:
    <canvas id="c" layoutsubtree width="100" height="100">
      <svg id="child" width="100" height="100">
        <rect width="100" height="100" fill="black" style="mask: url(#m);" />
      </svg>
    </canvas>
    
    <svg width="0" height="0">
      <mask id="m" maskContentUnits="objectBoundingBox">
        <a href="https://target-probe.example/">
          <rect width="1" height="1" fill="currentColor" />
        </a>
      </mask>
    </svg>
    
  3. Define CSS visited styles on the mask link:
    #m a:link { color: rgb(0, 0, 0); }
    #m a:visited { color: rgb(255, 255, 255); }
    
  4. After layout and paint settle, draw the element into the canvas:
    const ctx = document.getElementById('c').getContext('2d');
    ctx.drawElementImage(document.getElementById('child'), 0, 0);
    const alpha = ctx.getImageData(50, 50, 1, 1).data[3];
    
  5. If alpha === 255, the URL is visited in the current partition; if alpha === 0, it is unvisited.

Suggested Fix

Add a check in third_party/blink/renderer/core/paint/svg_mask_painter.cc (e.g., inside ResolveElementReference or PaintSVGMaskLayer), matching the logic used in svg_object_painter.cc:34-37, to ensure that when paint_flags & PaintFlag::kPrivacyPreserving is present, external mask resources not contained within the canvas subtree (!masker->GetElement()->IsInCanvasSubtree()) are rejected or forced to paint with unvisited styling.

Evaluated with Chrome root at commit: c6718ebbf2c677c6f63691e28c7801c563f6b19c


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker