Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Navigation
DescriptionInappropriate implementation in Navigation
ComponentNavigation
Bug ClassLogic Error
Tracker490139441
Fix commit446871399858 (chromium/src) +40/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-04-07

Changed Functions

FunctionChangeNotes
TEST_F
content/browser/renderer_host/navigation_request_unittest.cc
modified

Files Changed

  • content/browser/renderer_host/navigation_request.cc
  • content/browser/renderer_host/navigation_request_unittest.cc
From 446871399858d161c079f14e8f6440a080889d36 Mon Sep 17 00:00:00 2001
From: Mike West <[email protected]>
Date: Mon, 09 Mar 2026 02:12:25 -0700
Subject: [PATCH] Sanitize redirect chain when committing error pages.

When committing successful navigations, we execute
`SanitizeRedirectsForCommit(commit_params)` in order to reduce the
amount of cross-site information which might flow into a renderer. We
should do the same for error pages.

This CL copies the sanitization logic from
`NavigationRequest::CommitNavigation()` into
`NavigationRequest::CommitErrorPage()`, and adds a unittest based on
`NavigationRequestTest.SanitizeRedirectsForCommit`.

Bug: 490139441
Change-Id: I61ffc6550848a97dc036383ea9ddfce738472bff
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7642049
Reviewed-by: Nasko Oskov <[email protected]>
Commit-Queue: Mike West <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1596201}
---

diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 7ed0a1b..26d4c43 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -6510,6 +6510,13 @@
   // Use a separate cache shard, and no cookies, for error pages.
   isolation_info_for_subresources_ =
       net::IsolationInfo::CreateTransient(/*nonce=*/std::nullopt);
+
+  // Before sending the commit parameters to the renderer process, sanitize
+  // the redirect URLs to avoid leaking potentially sensitive data into
+  // processes which are cross-site. There is no dependency on the
+  // cross-site-ness, therefore just sanitize unilaterally.
+  SanitizeRedirectsForCommit(commit_params_);
+
   GetRenderFrameHost()->FailedNavigation(
       this, *common_params_, *commit_params_, has_stale_copy_in_cache_,
       net_error_, extended_error_code_, error_page_content, *document_token_);
diff --git a/content/browser/renderer_host/navigation_request_unittest.cc b/content/browser/renderer_host/navigation_request_unittest.cc
index 0c4f49a..30df8db02 100644
--- a/content/browser/renderer_host/navigation_request_unittest.cc
+++ b/content/browser/renderer_host/navigation_request_unittest.cc
@@ -876,6 +876,39 @@
   EXPECT_EQ(GURL("https://c.com"), commit_params->redirects[2]);
 }
 
+// Test to ensure that SanitizeRedirectsForCommit is called when a navigation
+// fails and commits an error page.
+TEST_F(NavigationRequestTest, SanitizeRedirectsForCommitErrorPage) {
+  const GURL start_url("https://a.com?param=1");
+  const GURL url_2("https://b.com?param=2#foo");
+  const GURL final_url("https://d.com?param=4");
+
+  std::unique_ptr<NavigationSimulator> navigation =
+      NavigationSimulator::CreateRendererInitiated(start_url, main_test_rfh());
+  navigation->Start();
+  navigation->Redirect(url_2);
+  navigation->Redirect(final_url);
+  navigation->Fail(net::ERR_CONNECTION_RESET);
+
+  NavigationRequest* request =
+      NavigationRequest::From(navigation->GetNavigationHandle());
+
+  // We expect the redirects in the NavigationRequest's commit_params_ to be
+  // sanitized.
+  const auto& commit_params = request->commit_params();
+
+  // redirects contains entries for A and B.
+  EXPECT_EQ(2u, commit_params.redirects.size());
+  EXPECT_EQ(GURL("https://a.com"), commit_params.redirects[0]);
+  EXPECT_EQ(GURL("https://b.com"), commit_params.redirects[1]);
+
+  // redirect_infos contains entries for B and D.
+  // The last entry (D) should NOT be sanitized.
+  EXPECT_EQ(2u, commit_params.redirect_infos.size());
+  EXPECT_EQ(GURL("https://b.com"), commit_params.redirect_infos[0].new_url);
+  EXPECT_EQ(final_url, commit_params.redirect_infos[1].new_url);
+}
+
 TEST_F(NavigationRequestTest, AbortsDeletedNavigationInProgress) {
   const GURL kUrl1 = GURL("http://a.com");
   std::unique_ptr<NavigationSimulator> navigation =
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/renderer_host/navigation_request_unittest.cc b/content/browser/renderer_host/navigation_request_unittest.cc
index 0c4f49a..30df8db02 100644
--- a/content/browser/renderer_host/navigation_request_unittest.cc
+++ b/content/browser/renderer_host/navigation_request_unittest.cc
@@ -876,6 +876,39 @@
   EXPECT_EQ(GURL("https://c.com"), commit_params->redirects[2]);
 }
 
+// Test to ensure that SanitizeRedirectsForCommit is called when a navigation
+// fails and commits an error page.
+TEST_F(NavigationRequestTest, SanitizeRedirectsForCommitErrorPage) {
+  const GURL start_url("https://a.com?param=1");
+  const GURL url_2("https://b.com?param=2#foo");
+  const GURL final_url("https://d.com?param=4");
+
+  std::unique_ptr<NavigationSimulator> navigation =
+      NavigationSimulator::CreateRendererInitiated(start_url, main_test_rfh());
+  navigation->Start();
+  navigation->Redirect(url_2);
+  navigation->Redirect(final_url);
+  navigation->Fail(net::ERR_CONNECTION_RESET);
+
+  NavigationRequest* request =
+      NavigationRequest::From(navigation->GetNavigationHandle());
+
+  // We expect the redirects in the NavigationRequest's commit_params_ to be
+  // sanitized.
+  const auto& commit_params = request->commit_params();
+
+  // redirects contains entries for A and B.
+  EXPECT_EQ(2u, commit_params.redirects.size());
+  EXPECT_EQ(GURL("https://a.com"), commit_params.redirects[0]);
+  EXPECT_EQ(GURL("https://b.com"), commit_params.redirects[1]);
+
+  // redirect_infos contains entries for B and D.
+  // The last entry (D) should NOT be sanitized.
+  EXPECT_EQ(2u, commit_params.redirect_infos.size());
+  EXPECT_EQ(GURL("https://b.com"), commit_params.redirect_infos[0].new_url);
+  EXPECT_EQ(final_url, commit_params.redirect_infos[1].new_url);
+}
+
 TEST_F(NavigationRequestTest, AbortsDeletedNavigationInProgress) {
   const GURL kUrl1 = GURL("http://a.com");
   std::unique_ptr<NavigationSimulator> navigation =
Loading diff…

Original Bug Report

reported by [email protected]

Compromised renderer -> read cross-origin redirect URLs

VULNERABILITY DETAILS

(LLM-assisted writeup)

vCross-Origin Information Leak: Missing Redirect Sanitization in Error Page Commits

When a navigation succeeds, NavigationRequest::CommitNavigation calls SanitizeRedirectsForCommit(commit_params_) to strip paths and query parameters from the redirects and redirect_infos arrays. This is an explicit security measure to “avoid leaking potentially sensitive data into processes which are cross-site”.

However, if a navigation fails (e.g., due to an aborted connection, a DNS error, or a server error) after one or more cross-origin redirects, NavigationRequest::CommitErrorPage is invoked. CommitErrorPage does NOT call SanitizeRedirectsForCommit. It directly calls GetRenderFrameHost()->FailedNavigation(..., *commit_params_, ...).

Location:

File: content/browser/renderer_host/navigation_request.cc

Function: NavigationRequest::CommitErrorPage (around line 6511)

Suggested Fix:

Call SanitizeRedirectsForCommit(commit_params_) inside NavigationRequest::CommitErrorPage() before calling GetRenderFrameHost()->FailedNavigation().

VERSION

Chrome: built from source, synced to HEAD

OS: Linux

REPRODUCTION CASE

  1. Assume compromised renderer. Apply the following patch to the renderer and rebuilt Chrome:
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 2cf52ebc1ce7c..f9c6e73da744a 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -3045,6 +3045,18 @@ void RenderFrameImpl::CommitFailedNavigation(
                frame_token_);
   RendererNavigationMetricsManager::Instance().MarkCommitStart(
       commit_params->navigation_metrics_token);
+
+  LOG(ERROR) << "VULNERABILITY POC - CommitFailedNavigation PID: " << getpid();
+  if (frame_->Top()->IsWebLocalFrame()) {
+    LOG(ERROR) << "VULNERABILITY POC - Top Frame Origin: " << frame_->Top()->ToWebLocalFrame()->GetSecurityOrigin().ToString().Utf8();
+  } else {
+    LOG(ERROR) << "VULNERABILITY POC - Top Frame is Remote.";
+  }
+
+  for (const auto& r : commit_params->redirects) {
+    LOG(ERROR) << "VULNERABILITY POC LEAKED URL: " << r.spec();
+  }
+
   DCHECK(navigation_client_impl_);
   DCHECK(!NavigationTypeUtils::IsSameDocument(common_params->navigation_type));
  1. Run the attached script. It will start two HTTP servers, representing an attacker site and a victim site, and it will run Chrome.
python3 attacker_server.py &
python3 victim_server.py &
sleep 2
out/Default/chrome --headless --disable-gpu --enable-logging=stderr --virtual-time-budget=5000 http://127.0.0.1:8001/ 2>&1 | grep --line-buffered "VULNERABILITY POC"

Output:

[2996188:1:0305/151902.299670:ERROR:content/renderer/render_frame_impl.cc:3049] VULNERABILITY POC - CommitFailedNavigation PID: 1
[2996188:1:0305/151902.300732:ERROR:content/renderer/render_frame_impl.cc:3051] VULNERABILITY POC - Top Frame Origin: http://127.0.0.1:8001
[2996188:1:0305/151902.301149:ERROR:content/renderer/render_frame_impl.cc:3057] VULNERABILITY POC LEAKED URL: http://127.0.0.1:8002/login
[2996188:1:0305/151902.301374:ERROR:content/renderer/render_frame_impl.cc:3057] VULNERABILITY POC LEAKED URL: http://127.0.0.1:8002/auth1
[2996188:1:0305/151902.301557:ERROR:content/renderer/render_frame_impl.cc:3057] VULNERABILITY POC LEAKED URL: http://127.0.0.1:8002/auth2?token=SUPER_SECRET_TOKEN_1337

CREDIT INFORMATION

Reporter credit: Ryan Lothian

View on issue tracker