blob: fcb6f8b9d7c18f836f4d1cce45358ca1d7e6d94f [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/connection_allowlist_utils.h"
#include <optional>
#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/time/time.h"
#include "base/values.h"
#include "content/browser/renderer_host/policy_container_host.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/connection_allowlist_util.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/connection_allowlist.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/gurl.h"
namespace {
void QueueConnectionAllowlistReport(
network::mojom::NetworkContext* network_context,
const GURL& url,
const GURL& context_url,
const net::NetworkAnonymizationKey& network_anonymization_key,
const std::optional<base::UnguessableToken>& reporting_source,
const std::string& group,
bool enforced) {
if (!network_context) {
return;
}
base::DictValue body;
body.Set("connection", url.GetAsReferrer().spec());
body.Set("disposition", enforced ? "enforce" : "report");
network_context->QueueReport("connection-allowlist", group, context_url,
reporting_source, network_anonymization_key,
std::move(body));
}
} // namespace
namespace content {
bool ResponseContainsConnectionAllowlist(
const network::mojom::URLResponseHead* response_head) {
return response_head && response_head->headers &&
response_head->parsed_headers &&
(response_head->parsed_headers->connection_allowlists.enforced
.has_value() ||
response_head->parsed_headers->connection_allowlists.report_only
.has_value());
}
bool HasActiveConnectionAllowlists(
const PolicyContainerPolicies& initiator_policies) {
if (!base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
return false;
}
return initiator_policies.connection_allowlists.enforced.has_value() ||
initiator_policies.connection_allowlists.report_only.has_value();
}
bool IsRedirectAllowedByConnectionAllowlist(
const PolicyContainerPolicies& initiator_policies,
const GURL& original_url,
network::mojom::NetworkContext* network_context,
const net::NetworkAnonymizationKey& network_anonymization_key,
const std::optional<base::UnguessableToken>& reporting_source) {
if (!base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
return true;
}
std::optional<base::UnguessableToken> resolved_reporting_source =
reporting_source.has_value()
? reporting_source
: initiator_policies.connection_allowlists.reporting_source;
// 1. Check report-only redirect behavior
if (initiator_policies.connection_allowlists.report_only.has_value() &&
initiator_policies.connection_allowlists.report_only->redirect_behavior ==
network::ConnectionAllowlist::RedirectBehavior::kBlock &&
initiator_policies.connection_allowlists.report_only->reporting_endpoint
.has_value()) {
QueueConnectionAllowlistReport(
network_context, original_url,
initiator_policies.connection_allowlists.response_url,
network_anonymization_key, resolved_reporting_source,
*initiator_policies.connection_allowlists.report_only
->reporting_endpoint,
/*enforced=*/false);
}
// 2. Check enforced behavior.
if (initiator_policies.connection_allowlists.enforced.has_value() &&
initiator_policies.connection_allowlists.enforced->redirect_behavior ==
network::ConnectionAllowlist::RedirectBehavior::kBlock) {
if (initiator_policies.connection_allowlists.enforced->reporting_endpoint
.has_value()) {
QueueConnectionAllowlistReport(
network_context, original_url,
initiator_policies.connection_allowlists.response_url,
network_anonymization_key, resolved_reporting_source,
initiator_policies.connection_allowlists.enforced->reporting_endpoint
.value(),
/*enforced=*/true);
}
return false;
}
return true;
}
bool ConnectionAllowlistAllowsUrlAndReportIfNeeded(
const PolicyContainerPolicies& policies,
const GURL& url,
network::mojom::NetworkContext* network_context,
const net::NetworkAnonymizationKey& network_anonymization_key,
const std::optional<base::UnguessableToken>& reporting_source) {
if (!base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
return true;
}
std::optional<base::UnguessableToken> resolved_reporting_source =
reporting_source.has_value()
? reporting_source
: policies.connection_allowlists.reporting_source;
// 1. Check report-only allowlist
if (policies.connection_allowlists.report_only.has_value()) {
if (!network::ConnectionAllowlistMatchesUrl(
policies.connection_allowlists.report_only.value(), url)) {
if (policies.connection_allowlists.report_only->reporting_endpoint
.has_value()) {
QueueConnectionAllowlistReport(
network_context, url, policies.connection_allowlists.response_url,
network_anonymization_key, resolved_reporting_source,
*policies.connection_allowlists.report_only->reporting_endpoint,
/*enforced=*/false);
}
}
}
// 2. Check enforced allowlist
if (policies.connection_allowlists.enforced.has_value()) {
if (network::ConnectionAllowlistMatchesUrl(
policies.connection_allowlists.enforced.value(), url)) {
return true;
}
if (policies.connection_allowlists.enforced->reporting_endpoint
.has_value()) {
QueueConnectionAllowlistReport(
network_context, url, policies.connection_allowlists.response_url,
network_anonymization_key, resolved_reporting_source,
*policies.connection_allowlists.enforced->reporting_endpoint,
/*enforced=*/true);
}
return false;
}
return true;
}
network::ConnectionAllowlists GetConnectionAllowlistsForWorker(
const GURL& response_url,
const network::mojom::URLResponseHead* response_head,
const PolicyContainerPolicies* creator_policies,
bool inherit_from_creator) {
if (!response_head ||
!base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
return network::ConnectionAllowlists();
}
// Local schemes inherit connection allowlists from their creator (e.g., the
// frame that registered the worker).
if (inherit_from_creator) {
return creator_policies ? creator_policies->connection_allowlists
: network::ConnectionAllowlists();
}
// For non-local schemes, the connection allowlist must be provided in the
// response headers.
if (ResponseContainsConnectionAllowlist(response_head)) {
return response_head->parsed_headers->connection_allowlists;
}
return network::ConnectionAllowlists();
}
bool FrameConnectionAllowlistAllowsRequestAndReportIfNeeded(
RenderFrameHost* render_frame_host,
const GURL& url,
bool is_redirect) {
if (!base::FeatureList::IsEnabled(network::features::kConnectionAllowlists)) {
return true;
}
if (!render_frame_host) {
return true;
}
// The feature currently does not impact fenced frames.
// TODO(crbug.com/447954811): Revisit this if the feature needs to be enabled
// and fenced frames need to be supported.
if (render_frame_host->IsNestedWithinFencedFrame()) {
return true;
}
const auto* rfh_impl =
static_cast<const RenderFrameHostImpl*>(render_frame_host);
if (!rfh_impl->HasPolicyContainerHost()) {
return true;
}
const PolicyContainerPolicies& policies =
rfh_impl->policy_container_host()->policies();
if (!HasActiveConnectionAllowlists(policies)) {
return true;
}
network::mojom::NetworkContext* network_context =
render_frame_host->GetProcess()
->GetStoragePartition()
->GetNetworkContext();
net::NetworkAnonymizationKey network_anonymization_key =
render_frame_host->GetIsolationInfoForSubresources()
.network_anonymization_key();
std::optional<base::UnguessableToken> reporting_source =
render_frame_host->GetReportingSource();
if (is_redirect) {
// For redirects, the connection allowlist either allows or blocks the
// redirect request based on its `redirects` directive. The request URL is
// irrelevant to the decision.
return IsRedirectAllowedByConnectionAllowlist(
policies, url, network_context, network_anonymization_key,
reporting_source);
}
return ConnectionAllowlistAllowsUrlAndReportIfNeeded(
policies, url, network_context, network_anonymization_key,
reporting_source);
}
} // namespace content