Medium chrome Input Validation 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Desktop
DescriptionImproper input validation in Desktop
ComponentDesktop
Bug ClassInput Validation
Tracker501886301
Fix commit90430e752b96 (chromium/src) +118/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-15

Changed Functions

FunctionChangeNotes
TEST_F
chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc
modified

Files Changed

  • chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc
  • chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler.cc
From 90430e752b9669b5054a31d2d4f539cd50ff75e4 Mon Sep 17 00:00:00 2001
From: Dhruv Kathpalia <dhruvkathpalia@google.com>
Date: Thu, 17 Sep 2026 16:44:55 -0700
Subject: [PATCH] Fail request if Google Calendar events contain invalid URLs

Validates that all URLs in Google Calendar events and attachments (event
link, conference URI, attachment file URL, and attachment icon link) are
valid and use HTTP or HTTPS schemes. If any event or attachment contains
an invalid or non-compliant URL, the request fails early and returns an
empty list of events. This prevents the Google Calendar module from
being displayed on the New Tab Page in broken, incomplete, or
inconsistent UI states.

Bug: b:501886301
Change-Id: I9c8d6665f2d127a3e0838a361ef54b5f212b599f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8408565
Commit-Queue: Dhruv Kathpalia <dhruvkathpalia@google.com>
Reviewed-by: Tibor Goldschwendt <tiborg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1700272}
---
diff --git a/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler.cc b/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler.cc
index 14ec608f6..f1b2d544 100644
--- a/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler.cc
+++ b/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler.cc
@@ -208,14 +208,35 @@
           google_apis::calendar::CalendarEvent::ResponseStatus::kDeclined) {
         continue;
       }
+      GURL event_url = GURL(event->html_link());
+      if (!event_url.is_valid() || !event_url.SchemeIsHTTPOrHTTPS()) {
+        return;
+      }
+
+      if (!event->conference_data_uri().is_empty() &&
+          (!event->conference_data_uri().is_valid() ||
+           !event->conference_data_uri().SchemeIsHTTPOrHTTPS())) {
+        return;
+      }
+
       ntp::calendar::mojom::CalendarEventPtr formatted_event =
           ntp::calendar::mojom::CalendarEvent::New();
       formatted_event->title = event->summary();
       formatted_event->start_time = event->start_time().date_time();
       formatted_event->end_time = event->end_time().date_time();
-      formatted_event->url = GURL(event->html_link());
+      formatted_event->url = event_url;
       formatted_event->location = event->location();
       for (const auto& attachment : event->attachments()) {
+        if (!attachment.icon_link().is_valid() ||
+            !attachment.icon_link().SchemeIsHTTPOrHTTPS()) {
+          return;
+        }
+        if (!attachment.file_url().is_empty() &&
+            (!attachment.file_url().is_valid() ||
+             !attachment.file_url().SchemeIsHTTPOrHTTPS())) {
+          return;
+        }
+
         ntp::calendar::mojom::AttachmentPtr formatted_attachment =
             ntp::calendar::mojom::Attachment::New();
         formatted_attachment->title = attachment.title();
diff --git a/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc b/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc
index f86ce21..de647fd 100644
--- a/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc
+++ b/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc
@@ -432,3 +432,99 @@
 
   EXPECT_EQ(response.size(), 3u);
 }
+
+TEST_F(GoogleCalendarPageHandlerTest, GetEventsFiltersPrivilegedSchemes) {
+  auto create_event = []() {
+    return base::DictValue()
+        .Set("kind", "calendar#event")
+        .Set("status", "confirmed")
+        .Set("htmlLink", "https://foo.com/valid")
+        .Set("created", "2018-05-14T18:55:59.000Z")
+        .Set("updated", "2021-03-17T10:42:53.637Z")
+        .Set("summary", "Test Event")
+        .Set("start", CreateEventTime(/*is_all_day_event=*/false,
+                                      /*is_end_time=*/false))
+        .Set("end", CreateEventTime(/*is_all_day_event=*/false,
+                                    /*is_end_time=*/true))
+        .Set("conferenceData", CreateConferenceData())
+        .Set("attendees", CreateAttendees(0));
+  };
+
+  auto test_invalid_event = [this](base::DictValue event) {
+    base::ListValue events;
+    events.Append(std::move(event));
+
+    base::DictValue result_dict =
+        base::DictValue()
+            .Set("kind", "calendar#events")
+            .Set("etag", "\"p32ofplf5q6gf20g\"")
+            .Set("summary", "test1@google.com")
+            .Set("updated", "2021-06-18T07:17:10.718Z")
+            .Set("timeZone", "America/Los_Angeles")
+            .Set("accessRole", "owner")
+            .Set("items", std::move(events));
+    std::string json;
+    JSONStringValueSerializer serializer(&json);
+    ASSERT_TRUE(serializer.Serialize(result_dict));
+
+    std::unique_ptr<GoogleCalendarPageHandler> handler =
+        CreateHandlerWithTestData(std::move(json));
+
+    std::vector<ntp::calendar::mojom::CalendarEventPtr> response;
+    base::MockCallback<GoogleCalendarPageHandler::GetEventsCallback> callback;
+    EXPECT_CALL(callback, Run(testing::_))
+        .Times(1)
+        .WillOnce(
+            [&](std::vector<ntp::calendar::mojom::CalendarEventPtr> events) {
+              response = std::move(events);
+            });
+
+    base::RunLoop run_loop;
+    handler->GetEvents(
+        google_apis::test_util::CreateQuitCallback(&run_loop, callback.Get()));
+    run_loop.Run();
+
+    // If any event or attachment has an invalid URL, the entire request fails
+    // and returns an empty list so the module is not displayed.
+    EXPECT_TRUE(response.empty());
+  };
+
+  // 1. Test invalid htmlLink.
+  {
+    base::DictValue event = create_event();
+    event.Set("htmlLink", "chrome://settings");
+    event.Set("attachments", CreateAttachments());
+    test_invalid_event(std::move(event));
+  }
+
+  // 2. Test invalid attachment fileUrl (with a valid iconLink).
+  {
+    base::DictValue invalid_attachment =
+        base::DictValue()
+            .Set("fileUrl", "chrome://settings")
+            .Set("title", "Attachment With Invalid File URL")
+            .Set("iconLink", "https://foo-icon.com/valid");
+    base::ListValue attachments;
+    attachments.Append(std::move(invalid_attachment));
+
+    base::DictValue event = create_event();
+    event.Set("attachments", std::move(attachments));
+    test_invalid_event(std::move(event));
+  }
+
+  // 3. Test invalid attachment iconLink (with a valid fileUrl).
+  {
+    base::DictValue invalid_attachment =
+        base::DictValue()
+            .Set("fileUrl", "https://foo-file.com/valid")
+            .Set("title", "Attachment With Invalid Icon Link")
+            .Set("iconLink", "chrome://image?url=https://attacker.example");
+    base::ListValue attachments;
+    attachments.Append(std::move(invalid_attachment));
+
+    base::DictValue event = create_event();
+    event.Set("attachments", std::move(attachments));
+    test_invalid_event(std::move(event));
+  }
+}
+
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc b/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc
index f86ce21..de647fd 100644
--- a/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc
+++ b/chrome/browser/new_tab_page/modules/v2/calendar/google_calendar_page_handler_unittest.cc
@@ -432,3 +432,99 @@
 
   EXPECT_EQ(response.size(), 3u);
 }
+
+TEST_F(GoogleCalendarPageHandlerTest, GetEventsFiltersPrivilegedSchemes) {
+  auto create_event = []() {
+    return base::DictValue()
+        .Set("kind", "calendar#event")
+        .Set("status", "confirmed")
+        .Set("htmlLink", "https://foo.com/valid")
+        .Set("created", "2018-05-14T18:55:59.000Z")
+        .Set("updated", "2021-03-17T10:42:53.637Z")
+        .Set("summary", "Test Event")
+        .Set("start", CreateEventTime(/*is_all_day_event=*/false,
+                                      /*is_end_time=*/false))
+        .Set("end", CreateEventTime(/*is_all_day_event=*/false,
+                                    /*is_end_time=*/true))
+        .Set("conferenceData", CreateConferenceData())
+        .Set("attendees", CreateAttendees(0));
+  };
+
+  auto test_invalid_event = [this](base::DictValue event) {
+    base::ListValue events;
+    events.Append(std::move(event));
+
+    base::DictValue result_dict =
+        base::DictValue()
+            .Set("kind", "calendar#events")
+            .Set("etag", "\"p32ofplf5q6gf20g\"")
+            .Set("summary", "test1@google.com")
+            .Set("updated", "2021-06-18T07:17:10.718Z")
+            .Set("timeZone", "America/Los_Angeles")
+            .Set("accessRole", "owner")
+            .Set("items", std::move(events));
+    std::string json;
+    JSONStringValueSerializer serializer(&json);
+    ASSERT_TRUE(serializer.Serialize(result_dict));
+
+    std::unique_ptr<GoogleCalendarPageHandler> handler =
+        CreateHandlerWithTestData(std::move(json));
+
+    std::vector<ntp::calendar::mojom::CalendarEventPtr> response;
+    base::MockCallback<GoogleCalendarPageHandler::GetEventsCallback> callback;
+    EXPECT_CALL(callback, Run(testing::_))
+        .Times(1)
+        .WillOnce(
+            [&](std::vector<ntp::calendar::mojom::CalendarEventPtr> events) {
+              response = std::move(events);
+            });
+
+    base::RunLoop run_loop;
+    handler->GetEvents(
+        google_apis::test_util::CreateQuitCallback(&run_loop, callback.Get()));
+    run_loop.Run();
+
+    // If any event or attachment has an invalid URL, the entire request fails
+    // and returns an empty list so the module is not displayed.
+    EXPECT_TRUE(response.empty());
+  };
+
+  // 1. Test invalid htmlLink.
+  {
+    base::DictValue event = create_event();
+    event.Set("htmlLink", "chrome://settings");
+    event.Set("attachments", CreateAttachments());
+    test_invalid_event(std::move(event));
+  }
+
+  // 2. Test invalid attachment fileUrl (with a valid iconLink).
+  {
+    base::DictValue invalid_attachment =
+        base::DictValue()
+            .Set("fileUrl", "chrome://settings")
+            .Set("title", "Attachment With Invalid File URL")
+            .Set("iconLink", "https://foo-icon.com/valid");
+    base::ListValue attachments;
+    attachments.Append(std::move(invalid_attachment));
+
+    base::DictValue event = create_event();
+    event.Set("attachments", std::move(attachments));
+    test_invalid_event(std::move(event));
+  }
+
+  // 3. Test invalid attachment iconLink (with a valid fileUrl).
+  {
+    base::DictValue invalid_attachment =
+        base::DictValue()
+            .Set("fileUrl", "https://foo-file.com/valid")
+            .Set("title", "Attachment With Invalid Icon Link")
+            .Set("iconLink", "chrome://image?url=https://attacker.example");
+    base::ListValue attachments;
+    attachments.Append(std::move(invalid_attachment));
+
+    base::DictValue event = create_event();
+    event.Set("attachments", std::move(attachments));
+    test_invalid_event(std::move(event));
+  }
+}
+
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.