High watch CVSS 8.1 chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Network
DescriptionIncorrect authorization in Network
ComponentNetwork
Sub-componentInternals>Services>Network
Bug ClassAuthorization Error
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.
Tracker553118043
Fix commit4568e184333b (chromium/src) +40/-0
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-08-26 (per vendor advisory)
Fix landed2026-08-28 (public git)
Disclosed2026-09-08
Patch gap11 days — fix public in source before the advisory

Background

`IsRequestHeaderSafe`
the services/network allowlist 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 the dcb (shared Brotli) and dcz (shared Zstd) Accept-Encoding tokens.
`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.

Key insight
The single mistake was trusting the renderer’s 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

  1. Compromised renderer An attacker who controls a renderer process crafts a network request through a path that lets it set the Accept-Encoding request header.
  2. Inject dictionary tokens The renderer inserts dcb or dcz (e.g. gzip, dcb or dcz;q=1) into the value, which IsRequestHeaderSafe previously accepted unchanged.
  3. Advertise unenabled compression The request goes out advertising shared dictionary compression even though the network stack did not enable it or select a dictionary.
  4. 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

An attacker gains the ability, from a compromised or malicious renderer process, to force shared-dictionary encoding tokens (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_unittest.cc
  • services/network/public/cpp/header_util.cc

Audit Directions

  • Renderer-controlled header allowlists
    Audit 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 parsing
    Verify 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 or q-values like dcz;q=1.
  • Trust-boundary-managed features
    Review 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.
From 4568e184333b8b92b07c135f11b10b3d811d8f36 Mon Sep 17 00:00:00 2001
From: Patrick Meenan <[email protected]>
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 <[email protected]>
Reviewed-by: Tsuyoshi Horo <[email protected]>
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},
Loading diff…

Regression Test / PoC

shipped with the fix
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},
Loading diff…

Original Bug Report

reported by [email protected]

ORB bypass and cross-origin leak via forged Compression Dictionary Transport headers

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A compromised renderer can attach Compression Dictionary Transport negotiation headers (Available-Dictionary, Dictionary-ID, and Accept-Encoding containing dcb/dcz) to cross-origin credentialed no-cors requests because they are not rejected at the Network Service boundary. When a server responds with delta-compressed content, the network stack treats the encoding as unknown and passes the raw stream through to Opaque Response Blocking (ORB) undecoded. ORB fails to match HTML/JSON signatures on the compressed binary bytes and fails open, allowing the compromised renderer to receive and decompress cross-origin responses.

Affected files:

  • services/network/public/cpp/header_util.cc
  • services/network/cors/cors_url_loader_factory.cc
  • services/network/orb/orb_impl.cc
  • net/filter/filter_source_stream.cc
  • net/shared_dictionary/shared_dictionary_network_transaction.cc

Estimated timestamp from git blame: 2024-05-23

Summary

A compromised renderer can potentially bypass Opaque Response Blocking (ORB) and read sensitive cross-origin responses by injecting Compression Dictionary Transport (RFC 9842) request headers (Available-Dictionary, Dictionary-ID, and Accept-Encoding: ..., dcb, dcz) into cross-origin no-cors requests. This leads to a cross-origin data disclosure / Site Isolation bypass.

Technical Details

  1. Boundary Validation Gap (services/network/public/cpp/header_util.cc): CorsURLLoaderFactory::IsValidRequest validates request headers via AreRequestHeadersSafe and ContainsForbiddenSecurityHeader (services/network/cors/cors_url_loader_factory.cc:904-918). ContainsForbiddenSecurityHeader only checks Sec- prefixed headers. AreRequestHeadersSafe checks against kUnsafeHeaders (services/network/public/cpp/header_util.cc:38-68), which does not include Accept-Encoding, Available-Dictionary, or Dictionary-ID. Consequently, a compromised renderer can attach these headers to any subresource request.

  2. Header Forwarding and Flag Absence (net/http/http_request_headers.cc, services/network/cors/cors_url_loader.cc):

    • HttpRequestHeaders::SetAcceptEncodingIfMissing (net/http/http_request_headers.cc:238-239) early-returns when Accept-Encoding is already set, preserving the renderer-supplied header.
    • For cross-origin no-cors requests, CorsURLLoader::StartRequest (services/network/cors/cors_url_loader.cc:364-376) does not attach net::LOAD_CAN_USE_SHARED_DICTIONARY. As a result, SharedDictionaryNetworkTransaction::Start (net/shared_dictionary/shared_dictionary_network_transaction.cc:142-145) executes in pure passthrough mode without fetching or registering local dictionaries.
  3. Undecoded Stream Passthrough (net/filter/filter_source_stream.cc): When the target server responds with Content-Encoding: dcb (or dcz), FilterSourceStream::ParseEncodingType (net/filter/filter_source_stream.cc:90-107) encounters an unlisted encoding in kEncodingMap and returns SourceStreamType::kUnknown. FilterSourceStream::GetContentEncodingTypes returns an empty vector (lines 127-132), causing URLRequestHttpJob::SetUpSourceStream (net/url_request/url_request_http_job.cc:1540-1568) to return the raw, undecoded network stream.

  4. ORB Fail-Open on Compressed Bytes (services/network/orb/orb_impl.cc):

    • For responses labeled text/html or application/json without X-Content-Type-Options: nosniff, OpaqueResponseBlockingAnalyzer::Init (services/network/orb/orb_impl.cc:317-325) returns Decision::kSniffMore.
    • The raw stream starts with dictionary compression magic bytes (0xFF 0x44 0x43 0x42 for dcb / 0x5E 0x2A 0x4D 0x18 for dcz). Sniffers in services/network/orb/orb_sniffers.cc fail to find HTML tags or JSON braces and return kNo.
    • At the end of the 1024-byte sniffing window, HandleEndOfSniffableResponseBody executes its fail-open fallback (services/network/orb/orb_impl.cc:472-474), returning Decision::kAllow.
    • The raw body pipe is delivered to the renderer via CorsURLLoader::OnReceiveResponse, where the CHECK at line 696 succeeds because did_use_shared_dictionary is false.

Potential Trigger Steps

(Note: These are suggested theoretical steps derived from static code analysis; automated tooling does not currently have live execution capability.)

  1. Compromise a renderer process hosting an attacker-controlled origin (https://attacker.example).
  2. Identify a target endpoint on https://victim.example/account that supports Compression Dictionary Transport (RFC 9842) for dynamic responses and does not send X-Content-Type-Options: nosniff or Cross-Origin-Resource-Policy.
  3. Anonymously retrieve the victim origin’s public dictionary asset (dict.dat), compute its SHA-256 hash, and format it as an RFC 8941 Structured Field byte sequence (:<base64_sha256>:).
  4. From the compromised renderer, send a Mojo IPC URLLoaderFactory::CreateLoaderAndStart request with:
    • url = https://victim.example/account
    • mode = mojom::RequestMode::kNoCors
    • credentials_mode = mojom::CredentialsMode::kInclude
    • client_side_content_decoding_enabled = false
    • headers = { 'Accept-Encoding': 'gzip, br, zstd, dcb, dcz', 'Available-Dictionary': ':<base64_sha256>:' }
  5. The Network Service forwards the forged headers verbatim along with ambient credentials (SameSite=None cookies).
  6. The victim server returns Content-Encoding: dcb delta-compressed against the public dictionary.
  7. The Network Service passes the raw stream through undecoded, ORB fails open to kAllow, and the raw compressed stream is piped to the renderer.
  8. The renderer decompresses the stream locally using dict.dat, disclosing the cross-origin plaintext response.

Suggested Fix

  1. Add Available-Dictionary and Dictionary-ID to kUnsafeHeaders in services/network/public/cpp/header_util.cc (or sanitize them in CorsURLLoaderFactory::IsValidRequest).
  2. Restrict renderer-specified Accept-Encoding headers that include dictionary encodings (dcb/dcz) unless authorized by the Network Service.
  3. Reject or treat as an error unexpected Content-Encoding: dcb/dcz responses when did_send_available_dictionary / LOAD_CAN_USE_SHARED_DICTIONARY is not set for the transaction.

Evaluated with Chrome root at commit: c6718ebbf2c677c6f63691e28c7801c563f6b19c


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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