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 <meacer@chromium.org>
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 <dibyapal@chromium.org>
Reviewed-by: Dana Fried <dfried@chromium.org>
Commit-Queue: Mustafa Emre Acer <meacer@chromium.org>
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