CVE-2026-3935
Overview
Background
- PWA app menu "App Info" item
- In a standalone web-app window,
WebAppMenuModelbuilds 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, whereGetAppShortName()is shown instead. - Minor text
- A secondary string attached to a
SimpleMenuModelitem viaSetMinorText(), whichMenuItemViewpaints with agfx::RenderTextnext 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
RenderTextdirectionality 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.
DIRECTIONALITY_AS_URL (forced LTR).Attack Path
- 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.
- 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).
- 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.
- 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
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.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
Audit Directions
- Origins rendered through generic text widgetsLook for other places that put a hostname or origin into labels, menu minor/secondary text, tooltips, dialog bodies or bubbles without
DIRECTIONALITY_AS_URLorurl_formatterelision helpers, especially in web-app, permission and download UI. - Model-to-view metadata propagationWhen 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.
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