Medium watch CVSS 4.3 chrome Spoofing 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in PictureInPicture
DescriptionIncorrect security UI in PictureInPicture
ComponentPictureInPicture
Bug ClassSpoofing
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.
Tracker474948986
Fix commit58a8c057b2b3 (chromium/src) +55/-5
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedBarath Stalin K
Reported2026-01-11 · bounty $3000 (per vendor advisory)
Fix landed2026-01-20 (public git)
Disclosed2026-03-10
Patch gap49 days — fix public in source before the advisory

Background

Picture-in-Picture (PiP) windows
always-on-top browser windows for Document PiP (PictureInPictureBrowserFrameView, whose title shows location_bar_model_->GetURLForDisplay()) and Video PiP (VideoOverlayWindowViews, whose origin label shows the source site).
Bidirectional (bidi) text / RTLO
Unicode layout rules that reorder right-to-left runs (and override characters such as Right-to-Left Override) when rendering, so the displayed order of an origin string can differ from its logical order.
`gfx::DirectionalityMode::DIRECTIONALITY_AS_URL`
a views::Label directionality mode, recommended by Chromium’s URL display guidelines, that always lays the text out left-to-right as a URL instead of inferring direction from the content.

Root Cause Analysis

Both PiP surfaces showed origin text in a views::Label created with the default directionality mode. The Document PiP title was built with views::Builder<views::Label>() followed by .SetText(location_bar_model_->GetURLForDisplay()), and the Video PiP origin label was constructed with only text, context and style. With the default mode, the label’s text direction follows its content. According to the commit message, RTL/RTLO characters in a malicious domain could therefore make the displayed origin look like a trusted domain, which violates the URL display guideline that origins must always render left-to-right.

The fix constructs both labels with gfx::DirectionalityMode::DIRECTIONALITY_AS_URL. The Document PiP title is now built with std::make_unique<views::Label>(GetURLForDisplay(), CONTEXT_LABEL, STYLE_PRIMARY, DIRECTIONALITY_AS_URL), and the Video PiP origin label gets the same fourth argument. The new tests check that the label direction is base::i18n::LEFT_TO_RIGHT both initially and after an RTL string (u"אבג") is set.

Key insight
The PiP origin labels let the text decide its own direction, so bidi characters in an attacker’s domain could reorder how the origin is shown. Creating the labels with DIRECTIONALITY_AS_URL forces left-to-right URL layout whatever the content is.

Attack Path

  1. Prepare a bidi-laden domain the attacker controls a domain that contains right-to-left characters, which the commit describes as RTLO characters in a malicious domain, chosen so that reordered display looks like a trusted site (inferred: the diff does not give a concrete hostname).
  2. Open a PiP window a page on that origin opens Document PiP or Video PiP, so the browser shows PictureInPictureBrowserFrameView’s window_title_ or VideoOverlayWindowViews’s origin label (inferred: the API entry points are not in the diff).
  3. Misleading origin display before the patch, the labels used content-derived directionality, so the bidi algorithm reordered the domain and the PiP window appeared to belong to a different, trusted origin.
  4. Social engineering the user trusts content shown in the PiP window because of the spoofed origin label (inferred).

Impact Assessment

The impact is an origin-spoofing security-UI bug in browser-drawn PiP window chrome, rated medium severity by the vendor. It requires the user to visit the attacker’s page and the page to open a Document or Video PiP window. Opening PiP normally requires a user gesture (inferred), and the changed code is the desktop views UI (chrome/browser/ui/views). It does not involve memory corruption, and the advisory text supplied here does not report in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
GetFadeAnimatorForTesting
chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
modified
FakeLinuxUiGetter
chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
modified
TEST_F
chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
modified

Files Changed

  • chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
  • chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
  • chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
  • chrome/browser/ui/views/overlay/video_overlay_window_views.cc
  • chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc

Audit Directions

  • Origin labels without `DIRECTIONALITY_AS_URL`
    search chrome/browser/ui/views for views::Labels that display GetURLForDisplay(), origins or hosts (permission bubbles, download, web-app, sharing and capture indicators) and are not created with gfx::DirectionalityMode::DIRECTIONALITY_AS_URL.
  • Builder-style label construction
    views::Builder<views::Label>() creates labels with the default directionality, so audit builder chains that SetText a URL or origin and do not pass a pre-constructed label.
  • Secondary window chrome
    check other non-tabbed browser windows (app windows, popups, overlays, Android/ChromeOS equivalents) that draw the source origin themselves, outside the hardened omnibox.
From 58a8c057b2b3b113e41ef8d8fb1228eb05d5a10c Mon Sep 17 00:00:00 2001
From: Benjamin Keen <[email protected]>
Date: Tue, 20 Jan 2026 14:54:43 -0800
Subject: [PATCH] Set Pip window title directionality mode to DIRECTIONALITY_AS_URL

This change addresses a security UI spoofing vulnerability in both
Document and Video Picture-in-Picture (PiP) windows where Right-to-Left
Override (RTLO) characters in a malicious domain could cause the
displayed origin to appear as a trusted domain.

The fix ensures that the `views::Label` used for the PiP window title
always renders text in a Left-to-Right (LTR) direction. This is achieved
by setting the label directionality mode to `DIRECTIONALITY_AS_URL`, as
suggested in
https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/url_display_guidelines/url_display_guidelines.md#rtl

Bug: 474948986
Change-Id: I56298ea3f267dfb5be8509addb30bbd0b34f1163
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7488911
Reviewed-by: Fr <[email protected]>
Commit-Queue: Benjamin Keen <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1571906}
---

diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
index cf08191..41457ef5 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
@@ -48,6 +48,7 @@
 #include "ui/gfx/animation/animation.h"
 #include "ui/gfx/animation/animation_container.h"
 #include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/text_constants.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/animation/animation_builder.h"
 #include "ui/views/animation/compositor_animation_runner.h"
@@ -595,9 +596,12 @@
 
   // Creates the window title.
   top_bar_container_view_->AddChildView(
-      views::Builder<views::Label>()
+      views::Builder<views::Label>(
+          std::make_unique<views::Label>(
+              location_bar_model_->GetURLForDisplay(),
+              views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY,
+              gfx::DirectionalityMode::DIRECTIONALITY_AS_URL))
           .CopyAddressTo(&window_title_)
-          .SetText(location_bar_model_->GetURLForDisplay())
           .SetHorizontalAlignment(gfx::ALIGN_LEFT)
           .SetElideBehavior(elide_behavior)
           .SetProperty(
@@ -1553,6 +1557,12 @@
   return window_title_;
 }
 
+void PictureInPictureBrowserFrameView::SetWindowTitleForTesting(  // IN-TEST
+    const std::u16string& title) {
+  CHECK(window_title_);
+  window_title_->SetText(title);
+}
+
 PictureInPictureWidgetFadeAnimator*
 PictureInPictureBrowserFrameView::GetFadeAnimatorForTesting() {
   return fade_animator_.get();
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
index b307ae5..33aad98 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
@@ -184,6 +184,7 @@
   views::View* GetBackToTabButtonForTesting();
   views::View* GetCloseButtonForTesting();
   views::Label* GetWindowTitleForTesting();
+  void SetWindowTitleForTesting(const std::u16string& title);
   PictureInPictureWidgetFadeAnimator* GetFadeAnimatorForTesting();
 
   // These values are persisted to logs. Entries should not be renumbered and
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
index 9b149cd..ac82d0d 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
@@ -4,6 +4,7 @@
 
 #include <optional>
 
+#include "base/i18n/rtl.h"
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/scoped_observation.h"
@@ -1085,6 +1086,26 @@
                     /*include_app_name=*/false));
 }
 
+IN_PROC_BROWSER_TEST_F(PictureInPictureBrowserFrameViewTest,
+                       WindowTitleHasCorrectDirectionality) {
+  ASSERT_NO_FATAL_FAILURE(SetUpDocumentPIP());
+  views::Label* window_title = pip_frame_view()->GetWindowTitleForTesting();
+  ASSERT_NE(nullptr, window_title);
+
+  // The directionality should be LTR to prevent spoofing.
+  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT,
+            window_title->GetTextDirectionForTesting());
+
+  // Set the window title to a RTL string.
+  const char16_t kRtl[] = u"אבג";
+  pip_frame_view()->SetWindowTitleForTesting(kRtl);
+  EXPECT_EQ(kRtl, window_title->GetText());
+
+  // The directionality should still be LTR.
+  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT,
+            window_title->GetTextDirectionForTesting());
+}
+
 #if BUILDFLAG(IS_LINUX)
 
 class FakeLinuxUiGetter : public ui::LinuxUiGetter {
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views.cc b/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
index 8ccc5385..315bbdb 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views.cc
@@ -64,6 +64,7 @@
 #include "ui/gfx/favicon_size.h"
 #include "ui/gfx/geometry/resize_utils.h"
 #include "ui/gfx/geometry/skia_conversions.h"
+#include "ui/gfx/text_constants.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/widget/widget_delegate.h"
@@ -1107,9 +1108,9 @@
   auto favicon_view = std::make_unique<views::ImageView>();
   favicon_view->SetSize(kFaviconSize);
 
-  auto origin = std::make_unique<views::Label>(std::u16string(),
-                                               views::style::CONTEXT_LABEL,
-                                               views::style::STYLE_BODY_4);
+  auto origin = std::make_unique<views::Label>(
+      std::u16string(), views::style::CONTEXT_LABEL, views::style::STYLE_BODY_4,
+      gfx::DirectionalityMode::DIRECTIONALITY_AS_URL);
   origin->SetEnabledColor(ui::kColorSysOnSurface);
   origin->SetBackgroundColor(SK_ColorTRANSPARENT);
   origin->SetHorizontalAlignment(gfx::ALIGN_LEFT);
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc b/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
index 6933ba2..0abc584 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
@@ -8,6 +8,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/i18n/rtl.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
@@ -1062,6 +1063,22 @@
   EXPECT_EQ(origin->GetText(), u"google.com");
 }
 
+TEST_F(VideoOverlayWindowViewsTest, OriginLabelHasCorrectDirectionality) {
+  views::Label* origin = overlay_window().origin_for_testing();
+  ASSERT_NE(nullptr, origin);
+
+  // The directionality should be LTR to prevent spoofing.
+  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());
+
+  // Set the source title to a RTL string.
+  const char16_t kRtl[] = u"אבג";
+  overlay_window().SetSourceTitle(kRtl);
+  EXPECT_EQ(kRtl, origin->GetText());
+
+  // The directionality should still be LTR to prevent spoofing.
+  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());
+}
+
 TEST_F(VideoOverlayWindowViewsTest,
        ControlsNeverHideWhileProgressBarIsDragged) {
   overlay_window().ShowInactive();
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
index 9b149cd..ac82d0d 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view_interactive_uitest.cc
@@ -4,6 +4,7 @@
 
 #include <optional>
 
+#include "base/i18n/rtl.h"
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
 #include "base/scoped_observation.h"
@@ -1085,6 +1086,26 @@
                     /*include_app_name=*/false));
 }
 
+IN_PROC_BROWSER_TEST_F(PictureInPictureBrowserFrameViewTest,
+                       WindowTitleHasCorrectDirectionality) {
+  ASSERT_NO_FATAL_FAILURE(SetUpDocumentPIP());
+  views::Label* window_title = pip_frame_view()->GetWindowTitleForTesting();
+  ASSERT_NE(nullptr, window_title);
+
+  // The directionality should be LTR to prevent spoofing.
+  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT,
+            window_title->GetTextDirectionForTesting());
+
+  // Set the window title to a RTL string.
+  const char16_t kRtl[] = u"אבג";
+  pip_frame_view()->SetWindowTitleForTesting(kRtl);
+  EXPECT_EQ(kRtl, window_title->GetText());
+
+  // The directionality should still be LTR.
+  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT,
+            window_title->GetTextDirectionForTesting());
+}
+
 #if BUILDFLAG(IS_LINUX)
 
 class FakeLinuxUiGetter : public ui::LinuxUiGetter {
diff --git a/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc b/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
index 6933ba2..0abc584 100644
--- a/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
+++ b/chrome/browser/ui/views/overlay/video_overlay_window_views_unittest.cc
@@ -8,6 +8,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/i18n/rtl.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
@@ -1062,6 +1063,22 @@
   EXPECT_EQ(origin->GetText(), u"google.com");
 }
 
+TEST_F(VideoOverlayWindowViewsTest, OriginLabelHasCorrectDirectionality) {
+  views::Label* origin = overlay_window().origin_for_testing();
+  ASSERT_NE(nullptr, origin);
+
+  // The directionality should be LTR to prevent spoofing.
+  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());
+
+  // Set the source title to a RTL string.
+  const char16_t kRtl[] = u"אבג";
+  overlay_window().SetSourceTitle(kRtl);
+  EXPECT_EQ(kRtl, origin->GetText());
+
+  // The directionality should still be LTR to prevent spoofing.
+  EXPECT_EQ(base::i18n::LEFT_TO_RIGHT, origin->GetTextDirectionForTesting());
+}
+
 TEST_F(VideoOverlayWindowViewsTest,
        ControlsNeverHideWhileProgressBarIsDragged) {
   overlay_window().ShowInactive();
Loading diff…

Original Bug Report

reported by [email protected]

Chrome Windows - PIP Window Displays Incorrect Origin When Domain Uses RTL Characters


Report description

Chrome Windows - PIP Window Displays Incorrect Origin When Domain Uses RTL Characters


Bug location

Where do you want to report your vulnerability?

Chrome VRP – Report security issues affecting the Chrome browser. See program rules


The problem

Please describe the technical details of the vulnerability

Summary

When a website uses Right-to-Left Override (RTLO) characters in its subdomain, the browser’s Picture-in-Picture (PiP) window displays an incorrect and misleading origin. Instead of showing the attacker-controlled root domain (eTLD+1), the PiP UI renders a spoofed trusted domain, allowing origin impersonation.

Steps to Reproduce

  1. Register or host a domain containing RTLO characters in the subdomain. POC URL:http://xn–mgb.accounts.login.apple.com.xn–mgb.https.google.com.summa.sbs/test.html
  2. Click the PIP button
  3. Observe the origin shown in the PiP window.
  4. Notice that the displayed origin shows a spoofed trusted domain (apple.com.I) instead of the real eTLD+1 (summa.sbs)

Expected Behavior The PiP window should always display the actual eTLD+1 of the media’s origin and sanitize or neutralize RTL characters to prevent misleading rendering.

Actual Behavior The PiP UI renders RTL characters in a way that causes the origin to appear as a different, trusted domain, hiding the attacker-controlled root domain.

Proof of Concept Displayed in PiP: apple.com.I

Actual media origin: summa.sbs

Impact analysis

An attacker can abuse RTL characters in a domain to make the Picture-in-Picture (PiP) window display a trusted brand name (for example, apple.com) while the actual content is served from an attacker-controlled domain (summa.sbs). Since PiP is a trusted, persistent UI element, users are likely to rely on the displayed origin for trust decisions. This enables convincing phishing, social-engineering, and malware delivery scenarios, as users may interact with or trust content believing it originates from a legitimate site. The absence of any alternative way to verify the true origin further increases the risk of exploitation.

Refer: https://issues.chromium.org/issues/40065117 https://issues.chromium.org/issues/40066780


The cause

What version of Chrome have you found the security issue in?

Version 143.0.7499.193 (Official Build) (64-bit)

No, it is not related to a crash.

Choose the type of vulnerability

Security UI Spoofing

How would you like to be publicly acknowledged for your report?

Barath Stalin K (https://www.linkedin.com/in/barathstalin/)

View on issue tracker