High watch CVSS 3.1 chrome Cross Origin 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in CORS
DescriptionInsufficient policy enforcement in CORS
ComponentCORS
Sub-componentCORS
Bug ClassCross Origin
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker498765210
Fix commitd513cd2fe668 (chromium/src) +5/-11
CWECWE-284
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-02 (per vendor advisory)
Fix landed2026-04-11 (public git)
Disclosed2026-04-15
Patch gap4 days — fix public in source before the advisory

Background

`no-cors` request mode
A fetch mode (mojom::RequestMode::kNoCors) that permits cross-origin requests with opaque responses and a restricted header/method surface, used for things like <img>, <script>, and fetch(..., {mode:'no-cors'}).
Forbidden method
HTTP methods the Fetch spec bars scripts from issuing (CONNECT, TRACE, TRACK), tested by cors::IsForbiddenMethod, because they enable connection hijacking or cross-site tracing attacks.
`CorsURLLoaderFactory`
The network-service component that validates each request (IsValidRequest) before it is dispatched and calls mojo::ReportBadMessage on an invalid one.
`IsForbiddenMethod` / `kConnectMethod`
Helper that matches the forbidden-method set, and the constant for the CONNECT method name used in the old special case.

Root Cause Analysis

In CorsURLLoaderFactory::IsValidRequest (named in the commit message; the enclosing function is not shown in the @@ header), the old guard only rejected a forbidden method when the request mode was not kNoCors, and in the kNoCors case rejected only CONNECT. The removed comment states this directly: forbidden methods were disallowed “for any requests except RequestMode::kNoCors,” with only CONNECT unconditionally blocked. This left TRACE and TRACK permitted whenever a request used no-cors mode, violating the Fetch invariant that forbidden methods must never be issued by web content regardless of mode.

The fix replaces the entire compound condition with a single if (cors::IsForbiddenMethod(request.method)), so all three forbidden methods are blocked in every mode before the request proceeds. This is a tightening of the check: the no-cors exemption for TRACE/TRACK is removed, as confirmed by the unit test flipping TRACE and TRACK from expect_allowed_for_no_cors = true to false.

Key insight
The validation carved out a no-cors-mode exception that allowed the forbidden methods TRACE and TRACK; the fix deletes the exception and blocks any forbidden method unconditionally.

Attack Path

  1. Issue a no-cors request Page script initiates a cross-origin request in no-cors mode (e.g. fetch(url, {mode:'no-cors', method:'TRACE'})). Ground: the removed condition only special-cased the kNoCors branch.
  2. Bypass the forbidden-method filter Because the method is TRACE or TRACK and the mode is kNoCors, the old guard’s second clause matched only CONNECT, so IsValidRequest returned without calling ReportBadMessage and the request was accepted. Ground: the pre-patch condition and the test rows {"TRACE", true} / {"TRACK", true}.
  3. Send the forbidden method cross-origin The network service dispatches a cross-origin TRACE/TRACK that the Fetch policy was supposed to forbid, enabling cross-site tracing behavior (inferred: potential for reflecting headers/cookies via the server’s trace echo).

Impact Assessment

A web page could cause the browser to send cross-origin TRACE/TRACK requests that the Fetch standard forbids, by selecting no-cors mode to evade the method filter in the network service. The advisory rates this High severity with impact “Insufficient policy enforcement in CORS”; it does not report in-the-wild exploitation. Any concrete escalation (e.g. Cross-Site Tracing / header reflection against a vulnerable server) depends on server behavior and is (inferred); the patch itself only demonstrates that the methods were wrongly permitted.

Changed Functions

FunctionChangeNotes
for
services/network/cors/cors_url_loader_unittest.cc
modified

Files Changed

  • services/network/cors/cors_url_loader_factory.cc
  • services/network/cors/cors_url_loader_unittest.cc

Audit Directions

  • Mode-conditional security checks
    Flag any policy check gated on RequestMode (especially kNoCors exemptions); verify the relaxation is spec-justified rather than an accidental hole.
  • Partial allow/deny lists
    Look for filters that special-case one element of a forbidden set (here only CONNECT) while silently permitting the rest; confirm the full set (cors::IsForbiddenMethod) is enforced.
  • Network-service request validation
    Review other IsValidRequest-style gates in services/network for methods, headers, or schemes that are validated differently across request modes.
From d513cd2fe6684b38bc02c3df59bfc5183bf4e1fd Mon Sep 17 00:00:00 2001
From: Kenichi Ishibashi <bashi@chromium.org>
Date: Fri, 10 Apr 2026 17:14:24 -0700
Subject: [PATCH] [CORS] Block forbidden methods for no-cors requests

Previously, forbidden methods like TRACE and TRACK were allowed when
the request mode was no-cors, and only CONNECT was unconditionally
blocked.

This CL updates CorsURLLoaderFactory::IsValidRequest to block all
forbidden methods regardless of the request mode. The unit test is
also updated to reflect this new restriction.

Bug: 498765210
Change-Id: Ie451a3c2b8fa7aafdebade8b3ba517be3ce255f8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7743444
Reviewed-by: mmenke <mmenke@chromium.org>
Commit-Queue: Kenichi Ishibashi <bashi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1613186}
---

diff --git a/services/network/cors/cors_url_loader_factory.cc b/services/network/cors/cors_url_loader_factory.cc
index 6a1eb07..bf02d66 100644
--- a/services/network/cors/cors_url_loader_factory.cc
+++ b/services/network/cors/cors_url_loader_factory.cc
@@ -910,13 +910,8 @@
     return false;
   }
 
-  // Don't allow forbidden methods for any requests except RequestMode::kNoCors.
-  // Don't allow CONNECT method for any request.
-  if ((request.mode != mojom::RequestMode::kNoCors &&
-       cors::IsForbiddenMethod(request.method)) ||
-      (request.mode == mojom::RequestMode::kNoCors &&
-       base::EqualsCaseInsensitiveASCII(
-           request.method, net::HttpRequestHeaders::kConnectMethod))) {
+  // Don't allow forbidden methods.
+  if (cors::IsForbiddenMethod(request.method)) {
     mojo::ReportBadMessage("CorsURLLoaderFactory: Forbidden method");
     return false;
   }
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index e9bbbc2..23a9e809 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -109,11 +109,10 @@
     std::string forbidden_method;
     bool expect_allowed_for_no_cors;
   } kTestCases[] = {
-      // CONNECT is never allowed, while TRACE and TRACK are allowed only with
-      // RequestMode::kNoCors.
+      // CONNECT, TRACE and TRACK are not allowed for any mode.
       {"CONNECT", false},
-      {"TRACE", true},
-      {"TRACK", true},
+      {"TRACE", false},
+      {"TRACK", false},
   };
   for (const auto& test_case : kTestCases) {
     SCOPED_TRACE(test_case.forbidden_method);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index e9bbbc2..23a9e809 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -109,11 +109,10 @@
     std::string forbidden_method;
     bool expect_allowed_for_no_cors;
   } kTestCases[] = {
-      // CONNECT is never allowed, while TRACE and TRACK are allowed only with
-      // RequestMode::kNoCors.
+      // CONNECT, TRACE and TRACK are not allowed for any mode.
       {"CONNECT", false},
-      {"TRACE", true},
-      {"TRACK", true},
+      {"TRACE", false},
+      {"TRACK", false},
   };
   for (const auto& test_case : kTestCases) {
     SCOPED_TRACE(test_case.forbidden_method);
Loading diff…

Original Bug Report

reported by vm...@google.com

Bypass of Site Isolation via TRACE method in kNoCors mode

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.

Overview: A logic flaw in the Network Service allows a compromised renderer to bypass forbidden HTTP method checks by sending a TRACE request in kNoCors mode. This causes cross-origin SameSite=None cookies to be reflected back in the response body, which is not blocked by Opaque Response Blocking (ORB), leading to cross-origin data exfiltration.

Affected files:

  • services/network/cors/cors_url_loader_factory.cc
  • services/network/orb/orb_impl.cc
  • services/network/public/cpp/cors/cors.cc
  • services/network/orb/orb_mimetypes.cc

Estimated timestamp from git blame: 2022-02-08

Summary

A compromised renderer process can potentially exfiltrate cross-origin SameSite=None and HttpOnly cookies from any target origin that has the HTTP TRACE method enabled. This is possible due to a gap between CORS validation and Opaque Response Blocking (ORB) in the Network Service. Specifically, CorsURLLoaderFactory allows the TRACE method for requests marked with kNoCors mode, and ORB defaults to failing-open for the resulting message/http reflected response.

Vulnerability Details

1. CORS Method Validation Bypass

In Blink, the Fetch API explicitly forbids methods like TRACE (third_party/blink/renderer/core/fetch/request.cc). However, a compromised renderer can bypass Blink’s checks by directly constructing and sending a network::ResourceRequest via the network::mojom::URLLoaderFactory Mojo interface.

When the Network Service receives this request, CorsURLLoaderFactory::IsValidRequest validates it. While it normally blocks forbidden methods, there is an exception for kNoCors mode:

// services/network/cors/cors_url_loader_factory.cc:913
if ((request.mode != mojom::RequestMode::kNoCors &&
     cors::IsForbiddenMethod(request.method)) ||
    (request.mode == mojom::RequestMode::kNoCors &&
     base::EqualsCaseInsensitiveASCII(
         request.method, net::HttpRequestHeaders::kConnectMethod))) {
  mojo::ReportBadMessage("CorsURLLoaderFactory: Forbidden method");
  return false;
}

By setting request.mode = kNoCors, the attacker bypasses the IsForbiddenMethod check. TRACE is allowed through, and because it is kNoCors, no CORS preflight is required.

2. Credential Attachment and TRACE Reflection

If the attacker also sets request.credentials_mode = kInclude, the network stack attaches the target cross-origin server’s SameSite=None cookies to the outgoing TRACE request. (Note: SameSite=Lax/Strict cookies are protected because the request is cross-site).

The HTTP TRACE method specification requires the server to echo the received request back to the client, typically with a Content-Type: message/http. This means the SameSite=None cookies are reflected in the response body.

3. ORB Fail-Open for message/http

Because the request is a cross-origin kNoCors request, the response is routed through Opaque Response Blocking (ORB).

  1. MIME Type: ORB categorizes message/http as MimeType::kOthers.
  2. Sniffing: The response body starts with the reflected request line (e.g., "TRACE / HTTP/1.1"). This does not match the blocklisted signatures for HTML, XML, or JSON, causing Sniff to return Decision::kSniffMore.
  3. Fail-Open Decision: Once sniffing concludes, OpaqueResponseBlockingAnalyzer::HandleEndOfSniffableResponseBody defaults to a fail-open state, returning Decision::kAllow.

Because ORB allows the response, the original Mojo data pipe containing the full response body (with the reflected cookies) is forwarded back to the compromised renderer, allowing the attacker to read them.

Potential Reproduction Steps

Note: These are suggested steps for a proof-of-concept, as our analysis is based on static code review.

  1. From a compromised renderer process, obtain a network::mojom::URLLoaderFactory remote (e.g., the default subresource loader factory).
  2. Call CreateLoaderAndStart with a network::ResourceRequest configured as follows:
    • url: https://target-with-trace-enabled.com/
    • method: TRACE
    • mode: kNoCors
    • credentials_mode: kInclude
  3. Capture the response using a network::mojom::URLLoaderClient.
  4. Read the raw response data pipe to extract the reflected request headers, including the victim’s cross-origin cookies.

Update CorsURLLoaderFactory::IsValidRequest to block all forbidden methods regardless of the RequestMode. The exception for kNoCors is unnecessary since the web platform (Fetch/XHR) does not allow TRACE or TRACK to be used even in no-cors mode.

if (cors::IsForbiddenMethod(request.method) ||
    base::EqualsCaseInsensitiveASCII(
         request.method, net::HttpRequestHeaders::kConnectMethod)) {
  mojo::ReportBadMessage("CorsURLLoaderFactory: Forbidden method");
  return false;
}

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker