CVE-2026-7968
Overview
Background
- CORS preflight
- the
OPTIONSrequest the browser sends before certain cross-origin requests to confirm the server permits them; skipping it where required weakens cross-origin protection. - `CorsPreflightPolicy::kPreventPreflight`
- a per-request policy value that asks the loader to skip the preflight, intended only for narrow, already-safe cases.
- `kIgnoreCorsPreflightPolicy`
- a (now-removed) feature flag that, when enabled, caused the loader to ignore
kPreventPreflightand always perform the normal CORS preflight decision.
Root Cause Analysis
cors_url_loader.cc, a block short-circuited the decision when request.cors_preflight_policy == mojom::CorsPreflightPolicy::kPreventPreflight, returning std::nullopt (no preflight required) — but only when the kIgnoreCorsPreflightPolicy flag was not enabled. In other words, honoring kPreventPreflight to skip preflight was the unsafe legacy behavior, and the flag (default-enabled) existed to suppress it and always apply the proper CORS checks. This CL removes the expired flag and, with it, the entire kPreventPreflight early-return block, so the loader no longer skips preflight based on that request policy; execution falls through to the normal method/header safelist checks (IsCorsSafelistedMethod, etc.). Because the flag had already been enabled by default, this is a cleanup that bakes in the secure behavior permanently rather than a behavior change at runtime. The net effect is that a request can no longer use kPreventPreflight to bypass a required CORS preflight.kPreventPreflight could suppress a required CORS preflight; the fix removes the expired kIgnoreCorsPreflightPolicy flag and the associated early-return so preflight is always evaluated on its merits.Attack Path
- Set the prevent-preflight policy
A request is issued with
cors_preflight_policy == kPreventPreflight. - Hit the legacy early-return (flag disabled)
With
kIgnoreCorsPreflightPolicydisabled, the helper returnedstd::nullopt, declaring no preflight needed and skipping theOPTIONScheck. - Issue the cross-origin request without preflight The request proceeds without the preflight that CORS would otherwise require, weakening cross-origin access enforcement (inferred; the diff shows the policy/flag removal, not a concrete data-exfiltration primitive).
Impact Assessment
kPreventPreflight, weakening cross-origin request validation in the network service; by this CL the mitigating flag was already default-enabled, so the change finalizes the secure behavior and deletes the dead flag. No specific cross-origin read/write primitive is demonstrated by the diff. The advisory rates it medium and classifies it as “Insufficient validation of untrusted input in CORS”; no in-the-wild exploitation is stated.Files Changed
services/network/cors/cors_url_loader.ccservices/network/public/cpp/features.ccservices/network/public/cpp/features.h
Audit Directions
- Request-policy preflight opt-outsAudit any remaining paths where a renderer-influenced request field can downgrade or skip CORS preflight, confirming they cannot bypass method/header safelist checks.
- Expired/retired feature flagsReview default-enabled security flags scheduled for removal to ensure removal deletes the insecure legacy branch rather than re-exposing it.
- `NeedsPreflight` early returnsTrace every
std::nullopt/earlyreturnin preflight-decision helpers to verify none allow a caller-controlled policy to skip required checks.
Patch
From 583163b3b6df95304e03dcb3a7d97f191d38072b Mon Sep 17 00:00:00 2001
From: Takashi Toyoshima <toyoshim@chromium.org>
Date: Fri, 07 Aug 2026 06:27:25 -0700
Subject: [PATCH] Remove network::features::kIgnoreCorsPreflightPolicy
Remove the expired feature flag kIgnoreCorsPreflightPolicy and its usage
in CorsURLLoader::NeedsPreflight.
Change-Id: Ia05b681fe92123d16022b20c34820037bbf72654
Bug: 497432281
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8214839
Reviewed-by: Adam Rice <ricea@chromium.org>
Commit-Queue: Takashi Toyoshima <toyoshim@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1675666}
---
diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc
index 1ff2b5d..7d31e958 100644
--- a/services/network/cors/cors_url_loader.cc
+++ b/services/network/cors/cors_url_loader.cc
@@ -94,12 +94,6 @@
return PreflightRequiredReason::kCorsWithForcedPreflightMode;
}
- if (!base::FeatureList::IsEnabled(features::kIgnoreCorsPreflightPolicy) &&
- request.cors_preflight_policy ==
- mojom::CorsPreflightPolicy::kPreventPreflight) {
- return std::nullopt;
- }
-
if (!IsCorsSafelistedMethod(request.method))
return PreflightRequiredReason::kDisallowedMethod;
diff --git a/services/network/public/cpp/features.cc b/services/network/public/cpp/features.cc
index b6c7b01..b65d81c 100644
--- a/services/network/public/cpp/features.cc
+++ b/services/network/public/cpp/features.cc
@@ -197,9 +197,6 @@
// and continue the handshake without sending one if requested.
BASE_FEATURE(kOmitCorsClientCert, base::FEATURE_DISABLED_BY_DEFAULT);
-// Ignore CorsPreflightPolicy and always perform CORS checks.
-BASE_FEATURE(kIgnoreCorsPreflightPolicy, base::FEATURE_ENABLED_BY_DEFAULT);
-
// Enforces that frame-type destinations require kNavigate mode.
BASE_FEATURE(kRestrictFrameDestinationsToNavigate,
base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/services/network/public/cpp/features.h b/services/network/public/cpp/features.h
index f1698cc3..432ae312 100644
--- a/services/network/public/cpp/features.h
+++ b/services/network/public/cpp/features.h
@@ -81,9 +81,6 @@
BASE_DECLARE_FEATURE(kOmitCorsClientCert);
COMPONENT_EXPORT(NETWORK_CPP_FLAGS_AND_SWITCHES)
-BASE_DECLARE_FEATURE(kIgnoreCorsPreflightPolicy);
-
-COMPONENT_EXPORT(NETWORK_CPP_FLAGS_AND_SWITCHES)
BASE_DECLARE_FEATURE(kRestrictFrameDestinationsToNavigate);
COMPONENT_EXPORT(NETWORK_CPP_FLAGS_AND_SWITCHES)
Original Bug Report
CORS preflight bypass via renderer-controlled cors_preflight_policy
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A compromised renderer can bypass Out-of-Renderer CORS (OOR-CORS) preflights by manipulating the cors_preflight_policy Mojo field. This allows attackers to send credentialed cross-origin requests with non-simple methods (e.g., DELETE) or headers without the mandatory OPTIONS preflight. This exposes cross-origin servers to state-changing CSRF attacks.
Affected files:
services/network/cors/cors_url_loader.ccservices/network/cors/cors_url_loader_factory.ccservices/network/public/mojom/url_request.mojom
Estimated timestamp from git blame: 2026-02-01
Description
There is a potential Out-of-Renderer CORS (OOR-CORS) bypass caused by missing validation of the cors_preflight_policy field when receiving a ResourceRequest from an untrusted renderer process.
The network::mojom::URLRequest Mojo struct includes a cors_preflight_policy field. When an untrusted renderer sends a request via URLLoaderFactory::CreateLoaderAndStart, the Network Service evaluates the request using CorsURLLoaderFactory::IsValidRequest() (in services/network/cors/cors_url_loader_factory.cc). While this function performs extensive validation on fields like load_flags and request_initiator, it completely ignores cors_preflight_policy.
Because this field is not sanitized, a compromised renderer can set it to mojom::CorsPreflightPolicy::kPreventPreflight. Later, when CorsURLLoader::NeedsPreflight() (in services/network/cors/cors_url_loader.cc) is called to determine if an OPTIONS preflight is necessary, it explicitly checks this field:
std::optional<PreflightRequiredReason> NeedsPreflight(
const ResourceRequest& request) {
// ...
if (request.cors_preflight_policy ==
mojom::CorsPreflightPolicy::kPreventPreflight) {
return std::nullopt;
}
// ... checks for IsCorsSafelistedMethod and CorsUnsafeNotForbiddenRequestHeaderNames
}
Returning std::nullopt forces the Network Service to skip the preflight check entirely. This allows an attacker to send credentialed cross-origin requests with non-safelisted methods (e.g., DELETE, PUT) or custom headers without triggering the CORS preflight, leading to CSRF vulnerabilities against APIs that rely on CORS preflight enforcement.
Potential Steps to Trigger
Note: Our tooling agent cannot run code, so these are suggested theoretical steps to exploit the vulnerability.
- Compromise Renderer: An attacker compromises a renderer process, gaining the ability to craft raw Mojo messages.
- Craft Request: The attacker uses their Mojo access to construct a
network::mojom::URLRequesttargeting a cross-origin state-changing endpoint (e.g.,https://target.com/api/delete-account). - Set Non-Simple Attributes: The attacker sets the HTTP
methodtoDELETEand setscredentials_modetokIncludeto send the victim’s ambient cookies. - Exploit the Policy Field: The attacker sets the
cors_preflight_policyfield tokPreventPreflight(enum value1). - Send the Request: The attacker sends the IPC via the
URLLoaderFactory::CreateLoaderAndStartendpoint provided to the renderer. - Bypass Preflight: The Network Service (
CorsURLLoaderFactory) accepts the request becauseIsValidRequestmisses this field.CorsURLLoaderseeskPreventPreflight, skips the OPTIONS request, and directly sends theDELETErequest with cookies to the target server. - CSRF Execution: The target server receives the
DELETErequest and processes it, assuming it was authorized by a successful preflight, resulting in a successful Cross-Site Request Forgery.
Suggested Fix
In CorsURLLoaderFactory::IsValidRequest() (services/network/cors/cors_url_loader_factory.cc), add a check to restrict cors_preflight_policy for untrusted callers. For example:
if (!is_trusted_) {
if (request.cors_preflight_policy != mojom::CorsPreflightPolicy::kConsiderPreflight) {
mojo::ReportBadMessage(
"CorsURLLoaderFactory: Untrusted caller attempting to bypass CORS preflight");
return false;
}
// ... existing checks ...
}
Alternatively, force the field to kConsiderPreflight for all requests originating from untrusted renderers.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.