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

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in WebAppInstalls
DescriptionIncorrect security UI in WebAppInstalls
ComponentWebAppInstalls
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.
Tracker479326680
Fix commit637553f660ab (chromium/src) +46/-0
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
CISA KEVNot listed
CreditedBarath Stalin K
Reported2026-01-28 (per vendor advisory)
Fix landed2026-02-27 (public git)
Disclosed2026-03-10
Patch gap11 days — fix public in source before the advisory

Background

PWA app menu "App Info" item
In a standalone web-app window, WebAppMenuModel builds an “App Info” menu entry whose minor text (the small right-hand label) shows the app’s origin, unless the app is an Isolated Web App, where GetAppShortName() is shown instead.
Minor text
A secondary string attached to a SimpleMenuModel item via SetMinorText(), which MenuItemView paints with a gfx::RenderText next to the item label.
Bidi / RTL hostnames
Hostnames can contain right-to-left characters (e.g. Arabic or Hebrew IDN labels); with default Unicode bidi resolution, the visual order of labels, dots and LTR parts can be rearranged so the rendered string doesn’t read in logical order.
`gfx::DIRECTIONALITY_AS_URL`
A RenderText directionality mode used for URL display that forces left-to-right base direction, so hostnames render in their true logical label order.

Root Cause Analysis

The commit message states that the PWA page-info/app menu showed the app’s origin as minor text next to “App Info” but “doesn’t handle RTL hostnames correctly”. Before the patch, the menu framework had no way to mark minor text as a URL: SimpleMenuModel items only carried a plain minor_text string. The paint code in ui/views/controls/menu/menu_item_view.cc only set horizontal alignment on the render_text before calling render_text->Draw(canvas), so the origin string was laid out with ordinary bidi resolution. A hostname containing RTL labels could therefore be shown with its components visually reordered, making the security-relevant origin indicator misleading.

The fix adds a minor_text_is_url flag that runs from SimpleMenuModel through MenuModel::GetMinorTextIsUrlAt() and MenuModelAdapter into MenuItemView. The web-app menu sets it for the non-IWA (origin-display) branch, and the painter then calls render_text->SetDirectionalityMode(gfx::DIRECTIONALITY_AS_URL), forcing LTR rendering of the origin.

Key insight
The origin of an installed web app was drawn as generic UI text, not as a URL, so bidi reordering of RTL hostnames could make the displayed origin differ visually from the real one. The fix tags that minor text as a URL so it is rendered with DIRECTIONALITY_AS_URL (forced LTR).

Attack Path

  1. Register an RTL hostname The attacker serves a web app from a domain whose hostname includes right-to-left (IDN) labels chosen so that default bidi layout rearranges the visible label order.
  2. Get the app installed The victim installs the attacker’s site as a PWA and opens it in a standalone app window (user action required).
  3. Open the app menu The victim opens the app window’s menu, where the “App Info” item shows the app origin as minor text through the unpatched path with no URL directionality.
  4. Misleading origin display The hostname renders in a bidi-reordered order, so the user may read it as a different or more trustworthy origin (inferred: the exact visual spoof depends on the chosen hostname).

Impact Assessment

An attacker who controls a web app with an RTL-containing hostname can make the origin label in the PWA app menu misrepresent the app’s real origin, a browser-UI spoofing issue with no memory corruption. Preconditions: the victim must install the attacker’s site as a web app and open its app-window menu, and the affected rendering path is the Views menu code (ui/views), so desktop platforms. The vendor advisory rates it medium severity (“Incorrect security UI in WebAppInstalls”); the advisory data provided does not report in-the-wild exploitation.

Files Changed

  • chrome/browser/ui/web_applications/web_app_menu_model.cc
  • ui/base/models/menu_model.cc
  • ui/base/models/menu_model.h
  • ui/menus/simple_menu_model.cc
  • ui/menus/simple_menu_model.h
  • ui/views/controls/menu/menu_item_view.cc
  • ui/views/controls/menu/menu_item_view.h
  • ui/views/controls/menu/menu_model_adapter.cc

Audit Directions

  • Origins rendered through generic text widgets
    Look for other places that put a hostname or origin into labels, menu minor/secondary text, tooltips, dialog bodies or bubbles without DIRECTIONALITY_AS_URL or url_formatter elision helpers, especially in web-app, permission and download UI.
  • Model-to-view metadata propagation
    When a model gains a rendering-semantics flag (URL, elide-from-head), check that every adapter (MenuModelAdapter, Mac/Cocoa menu bridges, other platform menu implementations) honors it. A path that drops the flag brings back the spoof.
  • Secondary labels and `GetSecondaryLabelAt`
    Other auxiliary text channels on MenuModel (secondary labels, accessible names) that may carry origins should get the same URL-directionality treatment.
From 637553f660ab74b759bc6848935df2a133311fab Mon Sep 17 00:00:00 2001
From: Mustafa Emre Acer <[email protected]>
Date: Thu, 26 Feb 2026 16:49:26 -0800
Subject: [PATCH] Render RTL URLs properly in the PWA page info menu

The PWA page info menu renders the App's origin as a minor text next
to the "App Info" menu item. Presently, this doesn't handle RTL
hostnames correctly. This CL fixes that by forcing directionality to
LTR.

Bug: 479326680
Change-Id: I421e2886e21dbdb9a8e8b298e3cebaa8de49955f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7608472
Reviewed-by: Dibyajyoti Pal <[email protected]>
Reviewed-by: Dana Fried <[email protected]>
Commit-Queue: Mustafa Emre Acer <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1591194}
---

diff --git a/chrome/browser/ui/web_applications/web_app_menu_model.cc b/chrome/browser/ui/web_applications/web_app_menu_model.cc
index e1d05c40..6957c75eb 100644
--- a/chrome/browser/ui/web_applications/web_app_menu_model.cc
+++ b/chrome/browser/ui/web_applications/web_app_menu_model.cc
@@ -180,6 +180,8 @@
           browser()->app_controller()->GetAppShortName();
       // For Isolated Web Apps, |GetAppShortName()| must be non-empty.
       display_text = short_name;
+    } else {
+      SetMinorTextIsUrlAt(app_info_index, true);
     }
     SetMinorText(app_info_index, display_text);
   }
diff --git a/ui/base/models/menu_model.cc b/ui/base/models/menu_model.cc
index 0ec16f06..264435ec 100644
--- a/ui/base/models/menu_model.cc
+++ b/ui/base/models/menu_model.cc
@@ -75,6 +75,10 @@
   return std::u16string();
 }
 
+bool MenuModel::GetMinorTextIsUrlAt(size_t index) const {
+  return false;
+}
+
 std::u16string MenuModel::GetSecondaryLabelAt(size_t index) const {
   return std::u16string();
 }
diff --git a/ui/base/models/menu_model.h b/ui/base/models/menu_model.h
index 4d2f358..3f92c25 100644
--- a/ui/base/models/menu_model.h
+++ b/ui/base/models/menu_model.h
@@ -86,6 +86,10 @@
   // is rendered to the right of the label and using the font GetLabelFontAt().
   virtual std::u16string GetMinorTextAt(size_t index) const;
 
+  // Returns true if the minor text at the specified index should be treated as
+  // a URL when rendering the menu item.
+  virtual bool GetMinorTextIsUrlAt(size_t index) const;
+
   // Returns the minor icon of the item at the specified index. The minor icon
   // is rendered to the left of the minor text.
   virtual ImageModel GetMinorIconAt(size_t index) const;
diff --git a/ui/menus/simple_menu_model.cc b/ui/menus/simple_menu_model.cc
index 156e506..069f500 100644
--- a/ui/menus/simple_menu_model.cc
+++ b/ui/menus/simple_menu_model.cc
@@ -355,6 +355,10 @@
   items_[ValidateItemIndex(index)].minor_text = minor_text;
 }
 
+void SimpleMenuModel::SetMinorTextIsUrlAt(size_t index, bool is_url) {
+  items_[ValidateItemIndex(index)].minor_text_is_url = is_url;
+}
+
 void SimpleMenuModel::SetMinorIcon(size_t index,
                                    const ui::ImageModel& minor_icon) {
   items_[ValidateItemIndex(index)].minor_icon = minor_icon;
@@ -462,6 +466,10 @@
   return items_[ValidateItemIndex(index)].minor_text;
 }
 
+bool SimpleMenuModel::GetMinorTextIsUrlAt(size_t index) const {
+  return items_[ValidateItemIndex(index)].minor_text_is_url;
+}
+
 ImageModel SimpleMenuModel::GetMinorIconAt(size_t index) const {
   return items_[ValidateItemIndex(index)].minor_icon;
 }
diff --git a/ui/menus/simple_menu_model.h b/ui/menus/simple_menu_model.h
index 8ddab95..78e2be4 100644
--- a/ui/menus/simple_menu_model.h
+++ b/ui/menus/simple_menu_model.h
@@ -227,6 +227,9 @@
   // Sets the minor text for the item at |index|.
   void SetMinorText(size_t index, const std::u16string& minor_text);
 
+  // Sets whether the minor text at |index| should be rendered as a URL.
+  void SetMinorTextIsUrlAt(size_t index, bool is_url);
+
   // Sets the minor icon for the item at |index|.
   void SetMinorIcon(size_t index, const ui::ImageModel& minor_icon);
 
@@ -277,6 +280,8 @@
   int GetCommandIdAt(size_t index) const override;
   std::u16string GetLabelAt(size_t index) const override;
   std::u16string GetMinorTextAt(size_t index) const override;
+  bool GetMinorTextIsUrlAt(size_t index) const override;
+
   ImageModel GetMinorIconAt(size_t index) const override;
   bool IsItemDynamicAt(size_t index) const override;
   // First defers to the delegate's GetAcceleratorForCommandId() method to
@@ -325,6 +330,7 @@
     std::u16string label;
     ui::Accelerator accelerator;
     std::u16string minor_text;
+    bool minor_text_is_url = false;
     ImageModel minor_icon;
     ImageModel icon;
     int group_id = -1;
diff --git a/ui/views/controls/menu/menu_item_view.cc b/ui/views/controls/menu/menu_item_view.cc
index 25653e1..e22ac23 100644
--- a/ui/views/controls/menu/menu_item_view.cc
+++ b/ui/views/controls/menu/menu_item_view.cc
@@ -551,6 +551,11 @@
   invalidate_dimensions();  // Triggers preferred size recalculation.
 }
 
+void MenuItemView::SetMinorTextIsUrl(bool is_url) {
+  minor_text_is_url_ = is_url;
+  invalidate_dimensions();  // Triggers preferred size recalculation.
+}
+
 void MenuItemView::SetMinorIcon(const ui::ImageModel& minor_icon) {
   minor_icon_ = minor_icon;
   invalidate_dimensions();  // Triggers preferred size recalculation.
@@ -1255,6 +1260,9 @@
     render_text->SetDisplayRect(minor_text_bounds);
     render_text->SetHorizontalAlignment(base::i18n::IsRTL() ? gfx::ALIGN_LEFT
                                                             : gfx::ALIGN_RIGHT);
+    if (GetMinorTextIsUrl()) {
+      render_text->SetDirectionalityMode(gfx::DIRECTIONALITY_AS_URL);
+    }
     render_text->Draw(canvas);
   }
 
@@ -1526,6 +1534,10 @@
              : minor_text_;
 }
 
+bool MenuItemView::GetMinorTextIsUrl() const {
+  return minor_text_is_url_;
+}
+
 ui::ImageModel MenuItemView::GetMinorIcon() const {
   return minor_icon_;
 }
diff --git a/ui/views/controls/menu/menu_item_view.h b/ui/views/controls/menu/menu_item_view.h
index c1b9193d..f620243 100644
--- a/ui/views/controls/menu/menu_item_view.h
+++ b/ui/views/controls/menu/menu_item_view.h
@@ -271,6 +271,9 @@
   // Sets the minor text.
   void SetMinorText(const std::u16string& minor_text);
 
+  // Sets whether the minor text should be rendered as a URL.
+  void SetMinorTextIsUrl(bool is_url);
+
   // Sets the minor icon.
   void SetMinorIcon(const ui::ImageModel& minor_icon);
 
@@ -527,6 +530,9 @@
   // item. This will be the accelerator (if one exists).
   std::u16string GetMinorText() const;
 
+  // Returns true if the minor text should be rendered as a URL.
+  bool GetMinorTextIsUrl() const;
+
   // Returns the icon that should be displayed to the left of the minor text.
   ui::ImageModel GetMinorIcon() const;
 
@@ -663,6 +669,7 @@
   std::u16string title_;
   std::u16string secondary_title_;
   std::u16string minor_text_;
+  bool minor_text_is_url_ = false;
   ui::ImageModel minor_icon_;
 
   // Does the title have a mnemonic? Only useful on the root menu item.
diff --git a/ui/views/controls/menu/menu_model_adapter.cc b/ui/views/controls/menu/menu_model_adapter.cc
index 3b477ca..9f78999d 100644
--- a/ui/views/controls/menu/menu_model_adapter.cc
+++ b/ui/views/controls/menu/menu_model_adapter.cc
@@ -139,6 +139,9 @@
   if (model->IsAlertedAt(model_index)) {
     menu_item_view->SetAlerted();
   }
+  if (model->GetMinorTextIsUrlAt(model_index)) {
+    menu_item_view->SetMinorTextIsUrl(true);
+  }
   menu_item_view->set_new_badge_type(model->GetNewBadgeTypeAt(model_index));
Loading diff…

Original Bug Report

reported by [email protected]

Incorrect Origin Display in PWA Install Prompt When Using RTL Characters


Report description

Chrome Mac - Incorrect Origin Display in PWA Install Prompt When Using 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 domain containing RTL (Right-to-Left) Unicode characters is used, the PWA install prompt displays an incorrect and misleading origin. Instead of showing the real origin “https://summa.sbs”, the install UI renders it as “https://xn--mgb.google.com”, causing a reversal and reordering of the hostname. This results in origin confusion during a high-trust installation flow.

Steps to Reproduce

  1. Open the POC URL in the Chrome MacOS - https://xn--mgb.google.com.xn--mgb.yen.summa.sbs/webapp.html
  2. Trigger the PWA install prompt.
  3. Observe the origin displayed in the install UI,App Info and Uninstall prompt

Expected Result

The install prompt should display the canonical, correctly ordered punycode origin: https://xn--mgb.google.com.xn--mgb.yen.summa.sbs

Actual Result

The install prompt displays a visually reordered and misleading origin: https://yen.summa.sbs.xn--mgb.google.com.xn–mgb This misrepresentation makes the origin appear related to a trusted domain.

Impact analysis

This issue enables origin spoofing during the PWA installation flow, where users rely heavily on the displayed origin to make trust decisions. An attacker can craft a malicious PWA using RTL characters to visually impersonate a trusted brand and trick users into installing it. Once installed, the PWA runs in a standalone, address-bar-less context, increasing the risk of credential phishing, persistent UI deception, and long-term user compromise. The impact is amplified because installation is a one-time trust action with lasting consequences.


The cause

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

144.0.7559.110 (Official Build) (arm64)

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://in.linkedin.com/in/barathstalin)

View on issue tracker