CVE-2026-3935
Overview
Files Changed
chrome/browser/ui/web_applications/web_app_menu_model.ccui/base/models/menu_model.ccui/base/models/menu_model.hui/menus/simple_menu_model.ccui/menus/simple_menu_model.hui/views/controls/menu/menu_item_view.ccui/views/controls/menu/menu_item_view.hui/views/controls/menu/menu_model_adapter.cc
Patch
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));
Original Bug Report
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
- Open the POC URL in the Chrome MacOS - https://xn--mgb.google.com.xn--mgb.yen.summa.sbs/webapp.html
- Trigger the PWA install prompt.
- 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)
Is the security issue related to a crash?
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)
- https://bughunters.google.com/about/rules/5745167867576320/chrome-vulnerability-reward-program-rules
- https://in.linkedin.com/in/barathstalin
- https://summa.sbs
- https://xn--mgb.google.com
- https://xn--mgb.google.com.xn--mgb.yen.summa.sbs
- https://xn--mgb.google.com.xn--mgb.yen.summa.sbs/webapp.html
- https://yen.summa.sbs.xn--mgb.google.com.xn--mgb