CVE-2026-14381
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
switchchrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc |
modified | |
WebAppInstallDialogDelegatechrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h |
modified | |
IsWidgetCurrentSizeSmallerThanPreferredSizechrome/browser/ui/views/web_apps/web_app_install_flow_dialog_delegate.cc |
modified |
Files Changed
chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.ccchrome/browser/ui/views/web_apps/web_app_diy_install_dialog.ccchrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.ccchrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.hchrome/browser/ui/views/web_apps/web_app_install_flow_dialog_delegate.cc
Patch
From 60b5af58de060bac04b05f5f3f19b5d1f07e722f Mon Sep 17 00:00:00 2001
From: Dan Murphy <dmurph@chromium.org>
Date: Fri, 26 Jun 2026 17:43:48 -0700
Subject: [PATCH] [WebApps] Add customized max shrinkage tolerance for PWA dialogs
When a PWA install or launch prompt is triggered simultaneously with
opening a constrained popup window (or dynamically resizing a popup),
the modal dialog's bounds are constrained by the parent window. When
occluded or clipped off-screen, critical security UI elements (app name,
icon, and initiating origin) are hidden, allowing attackers to spoof app
identity.
Previously, kMinBoundsForInstallDialog (50px) was used as a global
buffer to prevent macOS install dialogs from immediately self-closing
upon opening due to minor Cocoa sheet decorations and Retina DIP
rounding. Strict absolute minimum height checks caused regressions where
legitimate install prompts closed immediately on macOS presentation.
This change removes kMinBoundsForInstallDialog and introduces struct
MaxAllowedShrinkage { int max_width_shrinkage; int max_height_shrinkage;
}, allowing customized maximum shrinkage thresholds per dialog type.
Through manual testing and geometry logging, these are tuned to:
- Simple PWA dialogs: 40px width, 20px height shrinkage tolerance.
- Detailed PWA dialogs: 100px width, 150px height shrinkage tolerance.
- DIY and Launch dialogs: 50px width, 50px height shrinkage tolerance.
If constrained beyond these thresholds, dialogs automatically abort
reporting kIgnored.
WebAppLaunchDialogBrowserTest
TAG=agy
CONV=8d4eec4f-ab33-44b3-80f2-2a6e6f2b5704
Bug: 407283320
Test: Ran WebAppInstallDialogBrowserTest, WebAppInstallFlowBrowserTest,
Change-Id: I7309e578f4f3819c89a4f3b668617504228b8607
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7997505
Reviewed-by: Dibyajyoti Pal <dibyapal@chromium.org>
Commit-Queue: Daniel Murphy <dmurph@chromium.org>
Reviewed-by: Kunjan Patel <ksukh@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1653558}
---
diff --git a/chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc b/chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc
index 6d6bd3d0..19ce1354 100644
--- a/chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_detailed_install_dialog.cc
@@ -495,7 +495,8 @@
views::Widget* detailed_dialog_widget =
constrained_window::ShowWebModalDialogViews(dialog.release(),
web_contents);
- if (IsWidgetCurrentSizeSmallerThanPreferredSize(detailed_dialog_widget)) {
+ if (IsWidgetCurrentSizeSmallerThanPreferredSize(detailed_dialog_widget,
+ kDetailedMaxShrinkage)) {
delegate_weak_ptr->CloseDialogAsIgnored();
return;
}
diff --git a/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog.cc b/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog.cc
index 156ab2d..777f3e5 100644
--- a/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_diy_install_dialog.cc
@@ -171,7 +171,8 @@
views::Widget* diy_dialog_widget =
constrained_window::ShowWebModalDialogViews(dialog.release(),
web_contents);
- if (IsWidgetCurrentSizeSmallerThanPreferredSize(diy_dialog_widget)) {
+ if (IsWidgetCurrentSizeSmallerThanPreferredSize(diy_dialog_widget,
+ kDiyMaxShrinkage)) {
delegate_weak_ptr->CloseDialogAsIgnored();
return;
}
diff --git a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc
index e25a3be..676dadc 100644
--- a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.cc
@@ -111,8 +111,6 @@
}
} // namespace
-constexpr int kMinBoundsForInstallDialog = 50;
-
std::ostream& operator<<(std::ostream& os, InstallDialogType type) {
switch (type) {
case InstallDialogType::kSimple:
@@ -136,12 +134,26 @@
return normalized;
}
-bool IsWidgetCurrentSizeSmallerThanPreferredSize(views::Widget* widget) {
+MaxAllowedShrinkage GetMaxAllowedShrinkage(InstallDialogType type) {
+ switch (type) {
+ case InstallDialogType::kSimple:
+ return kSimpleMaxShrinkage;
+ case InstallDialogType::kDetailed:
+ return kDetailedMaxShrinkage;
+ case InstallDialogType::kDiy:
+ return kDiyMaxShrinkage;
+ }
+ return kSimpleMaxShrinkage;
+}
+
+bool IsWidgetCurrentSizeSmallerThanPreferredSize(
+ views::Widget* widget,
+ MaxAllowedShrinkage shrinkage) {
const gfx::Size& current_size = widget->GetSize();
const gfx::Size& preferred_size =
widget->GetContentsView()->GetPreferredSize();
- int min_width = preferred_size.width() - kMinBoundsForInstallDialog;
- int min_height = preferred_size.height() - kMinBoundsForInstallDialog;
+ int min_width = preferred_size.width() - shrinkage.max_width_shrinkage;
+ int min_height = preferred_size.height() - shrinkage.max_height_shrinkage;
return current_size.width() < min_width || current_size.height() < min_height;
}
@@ -300,7 +312,8 @@
void WebAppInstallDialogDelegate::OnWidgetBoundsChanged(
views::Widget* widget,
const gfx::Rect& new_bounds) {
- if (IsWidgetCurrentSizeSmallerThanPreferredSize(widget)) {
+ if (IsWidgetCurrentSizeSmallerThanPreferredSize(
+ widget, GetMaxAllowedShrinkage(dialog_type_))) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&WebAppInstallDialogDelegate::CloseDialogAsIgnored,
diff --git a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h
index bb517d28..25ed0136 100644
--- a/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h
+++ b/chrome/browser/ui/views/web_apps/web_app_install_dialog_delegate.h
@@ -63,6 +63,21 @@
// result in a weird filename), it only restricts what we suggest as titles.
std::u16string NormalizeSuggestedAppTitle(const std::u16string& title);
+// Defines the maximum allowed width and height shrinkage (in pixels) from the
+// preferred size of a dialog before it is considered too small/occluded and
+// automatically closed to prevent UI spoofing.
+struct MaxAllowedShrinkage {
+ int max_width_shrinkage;
+ int max_height_shrinkage;
+};
+
+inline constexpr MaxAllowedShrinkage kSimpleMaxShrinkage = {40, 20};
+inline constexpr MaxAllowedShrinkage kDetailedMaxShrinkage = {100, 150};
+inline constexpr MaxAllowedShrinkage kDiyMaxShrinkage = {50, 50};
+inline constexpr MaxAllowedShrinkage kLaunchMaxShrinkage = {50, 50};
+
+MaxAllowedShrinkage GetMaxAllowedShrinkage(InstallDialogType type);
+
// For some browser windows that are smaller in size, the install dialog's
// current size is smaller than the preferred size, leading to important
// security information being occluded. This function performs the comparison
@@ -70,7 +85,8 @@
// This serves as a stop-gap fix for crbug.com/384962294.
// TODO(crbug.com/346974105): Remove once tab modal dialogs can be sized
// irrespective of the size of the browser window triggering it.
-bool IsWidgetCurrentSizeSmallerThanPreferredSize(views::Widget* widget);
+bool IsWidgetCurrentSizeSmallerThanPreferredSize(views::Widget* widget,
+ MaxAllowedShrinkage shrinkage);
class WebAppInstallDialogDelegate : public WebAppModalDialogDelegate {
public:
diff --git a/chrome/browser/ui/views/web_apps/web_app_install_flow_dialog_delegate.cc b/chrome/browser/ui/views/web_apps/web_app_install_flow_dialog_delegate.cc
index c7774c3..8294b50 100644
--- a/chrome/browser/ui/views/web_apps/web_app_install_flow_dialog_delegate.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_install_flow_dialog_delegate.cc
@@ -223,8 +223,6 @@
return std::nullopt;
}
-constexpr int kMinBoundsForInstallDialog = 50;
-
} // namespace
WebAppInstallFlowDialogDelegate::WebAppInstallFlowDialogDelegate(
@@ -492,20 +490,11 @@
}
}
-bool WebAppInstallFlowDialogDelegate::
- IsWidgetCurrentSizeSmallerThanPreferredSize(views::Widget* widget) {
- const gfx::Size& current_size = widget->GetSize();
- const gfx::Size& preferred_size =
- widget->GetContentsView()->GetPreferredSize();
- int min_width = preferred_size.width() - kMinBoundsForInstallDialog;
- int min_height = preferred_size.height() - kMinBoundsForInstallDialog;
- return current_size.width() < min_width || current_size.height() < min_height;
-}
-
void WebAppInstallFlowDialogDelegate::OnWidgetBoundsChanged(
views::Widget* widget,
const gfx::Rect& new_bounds) {
- if (IsWidgetCurrentSizeSmallerThanPreferredSize(widget)) {
+ if (IsWidgetCurrentSizeSmallerThanPreferredSize(
+ widget, GetMaxAllowedShrinkage(dialog_type_))) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&WebAppInstallFlowDialogDelegate::CloseDialogAsIgnored,
@@ -905,8 +894,8 @@
views::Widget* widget = constrained_window::ShowWebModalDialogViews(
dialog.release(), web_contents);
Original Bug Report
App name & icon hidden when installation window resized small allowing potential spoofed name & icon
VULNERABILITY DETAILS this bug is Bypass of https://issues.chromium.org/issues/384962294
i use domain https://wiry-nova-floor.glitch.me/tpjack.html you can use long domain for example : https://auth.logins.account.new_________________.documents______.google.com.attacker.com or other. in pwa prompt the origin not shown.
when the pwa prompt is called at the same time as doing setInterval(‘window.resizeTo()’) this causes the origin to be invisible.
REPRODUCTION CASE
- open devtools
- do
window.open(“https://wiry-nova-floor.glitch.me/tpjack.html", “popup”, “toolbar=yes,scrollbars=yes,resizable=yes,top=400,left=500,width=500,height=190”);
- click once on web content then the window is resize and the pwa prompt is shown (the origin to be invisible)
VERSION Chrome Version 136.0.7099.0 (Official Build) canary (64 bit) Operating System: Window 11