CVE-2026-87499
Overview
Background
- `IsRequestHeaderSafe`
- the
services/networkallowlist check that decides whether a renderer-supplied HTTP request header name/value pair may be sent on a network request. - Shared dictionary compression
- a
net-stack feature where responses are compressed against a previously fetched dictionary, negotiated via thedcb(shared Brotli) anddcz(shared Zstd)Accept-Encodingtokens. - `Accept-Encoding` header
- a request header, legitimately set by the media pipeline (e.g.
identity;q=1, *;q=0), that advertises which content encodings the client will accept. - `net::HttpUtil::ValuesIterator`
- a helper that splits a comma-separated header value into individual tokens for per-value inspection.
Root Cause Analysis
IsRequestHeaderSafe in services/network/public/cpp/header_util.cc validated several sensitive request headers but placed no restriction on the encoding tokens a renderer could put in Accept-Encoding, even though shared dictionary compression is meant to be 100% managed by the network service and net stack. The violated invariant is that the renderer must never influence shared-dictionary negotiation, since the network stack alone knows whether a valid dictionary and the feature are actually available. Because the header passed through unchecked, a renderer could inject dcb or dcz tokens and advertise shared dictionary compression on requests where it was not really enabled, an incorrect-authorization condition where an untrusted process drives a decision reserved for a trusted one.
The fix iterates each comma-separated Accept-Encoding value with net::HttpUtil::ValuesIterator and rejects the header (return false) if any token starts with kSharedBrotliContentEncodingName or kSharedZstdContentEncodingName, while still permitting ordinary values like gzip and the media pipeline’s identity;q=1, *;q=0. This restores the boundary by making the trusted network stack the sole party that may add those tokens.
Accept-Encoding value wholesale, letting it advertise shared-dictionary encodings that only the network stack is authorized to negotiate; the fix closes this by explicitly filtering dcb/dcz tokens out of renderer-provided Accept-Encoding headers.Attack Path
- Compromised renderer
An attacker who controls a renderer process crafts a network request through a path that lets it set the
Accept-Encodingrequest header. - Inject dictionary tokens
The renderer inserts
dcbordcz(e.g.gzip, dcbordcz;q=1) into the value, whichIsRequestHeaderSafepreviously accepted unchanged. - Advertise unenabled compression The request goes out advertising shared dictionary compression even though the network stack did not enable it or select a dictionary.
- Server responds accordingly A server may then negotiate and return a shared-dictionary-compressed response outside the state the trusted net stack expects.
Impact Assessment
dcb/dcz) into outgoing requests, driving a negotiation the network service is supposed to solely control. The precondition is a renderer able to influence the Accept-Encoding request header on a request handled by the network service. The exposure is an incorrect-authorization/logic flaw in the Network component rather than a demonstrated memory-safety primitive from this diff alone.Files Changed
services/network/public/cpp/header_util.ccservices/network/public/cpp/header_util_unittest.cc
Audit Directions
- Renderer-controlled header allowlistsAudit every header-safety predicate (like
IsRequestHeaderSafe) for encodings, tokens, or directives that a trusted layer negotiates but the allowlist forwards verbatim from an untrusted process. - Comma-separated header parsingVerify that checks on list-valued headers (
Accept-Encoding,Connection,Transfer-Encoding) inspect each token with a proper iterator rather than the raw string, since a single-token match is easy to bypass with prefixes orq-values likedcz;q=1. - Trust-boundary-managed featuresReview features documented as fully owned by the network/net stack (shared dictionaries, compression, priority hints) to confirm no renderer-supplied input can toggle or advertise them.
Patch
From 4568e184333b8b92b07c135f11b10b3d811d8f36 Mon Sep 17 00:00:00 2001
From: Patrick Meenan <pmeenan@chromium.org>
Date: Thu, 27 Aug 2026 18:42:10 -0700
Subject: [PATCH] Block renderer-provided dcb or dcz Accept-Encoding headers
Dictionary compression is 100% handled in the network service and net
stack. This makes sure that renderer's don't accidentally try to
advertise dictionary compression when it's not really enabled.
Bug: 553118043
Change-Id: If9cdcf100520e0f74216daa80dc948e0f397748f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8300933
Commit-Queue: Patrick Meenan <pmeenan@chromium.org>
Reviewed-by: Tsuyoshi Horo <horo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1687719}
---
diff --git a/services/network/public/cpp/header_util.cc b/services/network/public/cpp/header_util.cc
index 402042a..a06abd25 100644
--- a/services/network/public/cpp/header_util.cc
+++ b/services/network/public/cpp/header_util.cc
@@ -18,6 +18,7 @@
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
+#include "net/shared_dictionary/shared_dictionary_constants.h"
#include "services/network/public/cpp/cors/cors.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
@@ -75,6 +76,26 @@
return false;
}
+ // The Accept-Encoding header can be set by the media pipeline (e.g.
+ // "identity;q=1, *;q=0"), but must not be used to negotiate shared
+ // dictionary compression (dcb, dcz) which is managed by the network stack.
+ if (base::EqualsCaseInsensitiveASCII(
+ key, net::HttpRequestHeaders::kAcceptEncoding)) {
+ net::HttpUtil::ValuesIterator encodings(value, ',');
+ while (encodings.GetNext()) {
+ if (base::StartsWith(
+ encodings.value(),
+ net::shared_dictionary::kSharedBrotliContentEncodingName,
+ base::CompareCase::INSENSITIVE_ASCII) ||
+ base::StartsWith(
+ encodings.value(),
+ net::shared_dictionary::kSharedZstdContentEncodingName,
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ return false;
+ }
+ }
+ }
+
// The Connection header is a comma-separated list of tokens. Per RFC 9110
// section 7.6.1, intermediaries treat each listed token as the name of a
// header to remove before forwarding, so only allow the connection-management
diff --git a/services/network/public/cpp/header_util_unittest.cc b/services/network/public/cpp/header_util_unittest.cc
index 9d2adf4..f4635e2 100644
--- a/services/network/public/cpp/header_util_unittest.cc
+++ b/services/network/public/cpp/header_util_unittest.cc
@@ -8,6 +8,7 @@
#include "base/strings/stringprintf.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
+#include "net/shared_dictionary/shared_dictionary_constants.h"
#include "services/network/public/cpp/features.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -28,6 +29,15 @@
{"Upgrade", "webbedsocket", false},
{"hOsT", "foo.test", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip", true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "identity;q=1, *;q=0", true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip, identity;q=1, *;q=0",
+ true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcb", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcz", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip, dcb", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcz;q=1", false},
+
{net::HttpRequestHeaders::kConnection, "Upgrade", false},
{net::HttpRequestHeaders::kConnection, "Close", true},
{net::HttpRequestHeaders::kConnection, "keep-alive", true},
@@ -92,6 +102,15 @@
{net::HttpRequestHeaders::kTransferEncoding, "gzip", false},
{"Set-Cookie", "foo=bar", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip", true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "identity;q=1, *;q=0", true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip, identity;q=1, *;q=0",
+ true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcb", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcz", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip, dcb", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcz;q=1", false},
+
{net::HttpRequestHeaders::kConnection, "Upgrade", false},
{net::HttpRequestHeaders::kConnection, "Close", true},
{net::HttpRequestHeaders::kConnection, "keep-alive", true},
Regression Test / PoC
diff --git a/services/network/public/cpp/header_util_unittest.cc b/services/network/public/cpp/header_util_unittest.cc
index 9d2adf4..f4635e2 100644
--- a/services/network/public/cpp/header_util_unittest.cc
+++ b/services/network/public/cpp/header_util_unittest.cc
@@ -8,6 +8,7 @@
#include "base/strings/stringprintf.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
+#include "net/shared_dictionary/shared_dictionary_constants.h"
#include "services/network/public/cpp/features.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -28,6 +29,15 @@
{"Upgrade", "webbedsocket", false},
{"hOsT", "foo.test", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip", true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "identity;q=1, *;q=0", true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip, identity;q=1, *;q=0",
+ true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcb", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcz", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip, dcb", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcz;q=1", false},
+
{net::HttpRequestHeaders::kConnection, "Upgrade", false},
{net::HttpRequestHeaders::kConnection, "Close", true},
{net::HttpRequestHeaders::kConnection, "keep-alive", true},
@@ -92,6 +102,15 @@
{net::HttpRequestHeaders::kTransferEncoding, "gzip", false},
{"Set-Cookie", "foo=bar", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip", true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "identity;q=1, *;q=0", true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip, identity;q=1, *;q=0",
+ true},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcb", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcz", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "gzip, dcb", false},
+ {net::HttpRequestHeaders::kAcceptEncoding, "dcz;q=1", false},
+
{net::HttpRequestHeaders::kConnection, "Upgrade", false},
{net::HttpRequestHeaders::kConnection, "Close", true},
{net::HttpRequestHeaders::kConnection, "keep-alive", true},