Low chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactConfused deputy in DeviceBoundSessionCredentials
DescriptionConfused deputy in DeviceBoundSessionCredentials
ComponentDeviceBoundSessionCredentials
Sub-componentDBSC
Bug ClassAuthorization Error
Tracker500532594
Fix commitc3341a0216ec (chromium/src) +164/-25
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
GURL
net/device_bound_sessions/session_binding_utils.h
modified
Origin
net/device_bound_sessions/session_binding_utils.h
modified
Time
net/device_bound_sessions/session_binding_utils.h
modified
TEST_F
net/device_bound_sessions/registration_fetcher_unittest.cc
modified
if
net/device_bound_sessions/registration_fetcher_unittest.cc
modified
TEST
net/device_bound_sessions/session_binding_utils_unittest.cc
modified

Files Changed

  • net/device_bound_sessions/registration_fetcher_unittest.cc
  • net/device_bound_sessions/session_binding_utils.h
  • net/device_bound_sessions/session_binding_utils.cc
  • net/device_bound_sessions/registration_fetcher.cc
  • net/device_bound_sessions/session_binding_utils_unittest.cc
From c3341a0216ecfb6af25cdd85ff7d3e0a56578c12 Mon Sep 17 00:00:00 2001
From: Ali Hijazi <ahijazi@chromium.org>
Date: Mon, 07 Sep 2026 12:09:15 -0700
Subject: [PATCH] [DBSC] Derive .well-known URLs strictly from canonical origins

Previously, RegistrationFetcherImpl constructed .well-known discovery
URLs using GURL::ReplaceComponents on endpoint URLs. This introduced two
security issues:

1. In the subdomain registration flow, replacing only the host and path
   on the subdomain fetcher endpoint preserved the subdomain's port,
   query parameters, and fragments. If an attacker or untrusted tenant
   controlled a service on a custom port on a subdomain (e.g. :9443),
   it could force Chrome to query the apex domain on that same custom
   port (:9443) rather than the apex origin's port. If the apex had an
   attacker-controllable service on that port, it could authorize the
   subdomain to register sessions for the entire site.

2. In federated flows, query parameters and fragments from provider or
   relying party endpoints were unintentionally forwarded to the
   .well-known endpoints.

This change introduces CreateWellKnownUrl(const url::Origin&) in
session_binding_utils to deterministically resolve
"/.well-known/device-bound-sessions" from an authoritative url::Origin.
Because url::Origin only consists of (scheme, host, port), all query
parameters, fragments, userinfo, and extraneous path components are
guaranteed to be stripped, and the apex check strictly queries
session->origin() rather than inheriting port/query state from the
subdomain. All three .well-known URL construction sites in
RegistrationFetcher are updated to use this helper. Test fixtures in
registration_fetcher_unittest are parameterized to supply dynamic server
ports in scope origins, and regression tests are added to verify
query/fragment stripping.

TAG=agy

Fixed: 500532594
Change-Id: I43aa32c99fa2a1fe63afc45a379c41970b0dbee1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8344409
Reviewed-by: Alex Ilin <alexilin@chromium.org>
Commit-Queue: Ali Hijazi <ahijazi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1693428}
---
diff --git a/net/device_bound_sessions/registration_fetcher.cc b/net/device_bound_sessions/registration_fetcher.cc
index 0c3a5619..44b0cec03 100644
--- a/net/device_bound_sessions/registration_fetcher.cc
+++ b/net/device_bound_sessions/registration_fetcher.cc
@@ -545,9 +545,8 @@
       return;
     }
 
-    GURL::Replacements replacements;
-    replacements.SetPathStr("/.well-known/device-bound-sessions");
-    GURL well_known_url = provider_url_.ReplaceComponents(replacements);
+    GURL well_known_url =
+        CreateWellKnownUrl(url::Origin::Create(provider_url_));
     // TODO(crbug.com/495096658): Assert that `IsForRefreshRequest()` is false
     // once the tests are fixed.
     url_fetcher_ = std::make_unique<URLFetcher>(
@@ -638,9 +637,8 @@
       return;
     }
 
-    GURL::Replacements replacements;
-    replacements.SetPathStr("/.well-known/device-bound-sessions");
-    GURL well_known_url = fetcher_endpoint_.ReplaceComponents(replacements);
+    GURL well_known_url =
+        CreateWellKnownUrl(url::Origin::Create(fetcher_endpoint_));
     url_fetcher_ = std::make_unique<URLFetcher>(
         context_, well_known_url, referring_origin_, net_log_source_,
         IsForRefreshRequest());
@@ -1019,11 +1017,7 @@
         // rather the top-level site (which matches the origin when including
         // the site).
         final_registration_url.host() != session->origin().host()) {
-      GURL::Replacements replacements;
-      replacements.SetPathStr("/.well-known/device-bound-sessions");
-      replacements.SetHostStr(session->origin().host());
-      GURL well_known_url =
-          fetcher_endpoint_.ReplaceComponents(std::move(replacements));
+      GURL well_known_url = CreateWellKnownUrl(session->origin());
       url_fetcher_ = std::make_unique<URLFetcher>(
           context_, well_known_url, referring_origin_, net_log_source_,
           /*is_refresh=*/false);
diff --git a/net/device_bound_sessions/session_binding_utils.cc b/net/device_bound_sessions/session_binding_utils.cc
index 2f4961a..9f316a7 100644
--- a/net/device_bound_sessions/session_binding_utils.cc
+++ b/net/device_bound_sessions/session_binding_utils.cc
@@ -26,6 +26,7 @@
 #include "third_party/boringssl/src/include/openssl/bn.h"
 #include "third_party/boringssl/src/include/openssl/ecdsa.h"
 #include "url/gurl.h"
+#include "url/origin.h"
 
 namespace net::device_bound_sessions {
 
@@ -241,4 +242,13 @@
   NOTREACHED();
 }
 
+constexpr char kWellKnownPath[] = "/.well-known/device-bound-sessions";
+
+GURL CreateWellKnownUrl(const url::Origin& origin) {
+  if (origin.opaque()) {
+    return GURL();
+  }
+  return origin.GetURL().Resolve(kWellKnownPath);
+}
+
 }  // namespace net::device_bound_sessions
diff --git a/net/device_bound_sessions/session_binding_utils.h b/net/device_bound_sessions/session_binding_utils.h
index ccba559..cdd7c2f 100644
--- a/net/device_bound_sessions/session_binding_utils.h
+++ b/net/device_bound_sessions/session_binding_utils.h
@@ -20,6 +20,10 @@
 
 class GURL;
 
+namespace url {
+class Origin;
+}
+
 namespace base {
 class Time;
 }
@@ -77,6 +81,10 @@
     const url::Origin& referring_origin,
     const GURL& target_url);
 
+// Returns the canonical .well-known/device-bound-sessions URL for `origin`.
+// Returns an empty GURL if `origin` is opaque.
+GURL NET_EXPORT CreateWellKnownUrl(const url::Origin& origin);
+
 // If `request` does not have a session usage listed for `session_key`, or if
 // that session usage is smaller than `new_usage`, then `new_usage` will be set
 // as the new session usage for `session_key`.
diff --git a/net/device_bound_sessions/registration_fetcher_unittest.cc b/net/device_bound_sessions/registration_fetcher_unittest.cc
index 1b16dea..951fe0c 100644
--- a/net/device_bound_sessions/registration_fetcher_unittest.cc
+++ b/net/device_bound_sessions/registration_fetcher_unittest.cc
@@ -62,6 +62,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/functional/overload.h"
+#include "third_party/abseil-cpp/absl/strings/str_format.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -114,6 +115,32 @@
   }]
 })";
 
+std::string GetSubdomainValidJsonWithCustomPort(int port) {
+  constexpr char kSubdomainValidJson[] =
+      R"({
+  "session_identifier": "session_id",
+  "refresh_url": "/refresh",
+  "scope": {
+    "origin": "https://a.test:%v",
+    "include_site": true,
+    "scope_specification" : [
+      {
+        "type": "include",
+        "domain": "trusted.a.test",
+        "path": "/only_trusted_path"
+      }
+    ]
+  },
+  "credentials": [{
+    "type": "cookie",
+    "name": "auth_cookie",
+    "attributes": "Domain=a.test; Path=/; Secure; SameSite=None"
+  }]
+})";
+
+  return absl::StrFormat(kSubdomainValidJson, port);
+}
+
 constexpr char kSubdomainValidJsonIncludeSiteFalse[] =
     R"({
   "session_identifier": "session_id",
@@ -305,6 +332,13 @@
   return response;
 }
 
+std::unique_ptr<test_server::HttpResponse> ReturnSubdomainResponse(
+    const test_server::EmbeddedTestServer* server,
+    const test_server::HttpRequest& request) {
+  return ReturnResponse(
+      HTTP_OK, GetSubdomainValidJsonWithCustomPort(server->port()), request);
+}
+
 std::unique_ptr<test_server::HttpResponse> ReturnChallengeResponse(
     HttpStatusCode code,
     std::string_view challenge) {
@@ -3070,8 +3104,8 @@
                           R"json({
                             "registering_origins": [ "https://subdomain.a.test:$1" ]
                           })json")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3100,8 +3134,8 @@
   server_.RegisterRequestHandler(base::BindRepeating(
       &ReturnForHostAndPath, "a.test", "/.well-known/device-bound-sessions",
       base::BindRepeating(&ReturnResponse, HTTP_BAD_REQUEST, "")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3132,8 +3166,8 @@
   server_.RegisterRequestHandler(base::BindRepeating(
       &ReturnForHostAndPath, "a.test", "/.well-known/device-bound-sessions",
       base::BindRepeating(&ReturnResponse, HTTP_OK, "invalid JSON")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3165,8 +3199,8 @@
       &ReturnForHostAndPath, "a.test", "/.well-known/device-bound-sessions",
       base::BindRepeating(&ReturnResponse, HTTP_OK,
                           "{\"registering_origins\": [ 12345 ]}")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3200,8 +3234,8 @@
                           R"json({
                             "registering_origins": [ "https://subdomain.a.test:$1" ]
                           })json")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("not-allowed-subdomain.a.test", "/");
@@ -3268,8 +3302,8 @@
                               "https://other-subdomain.a.test:$1"
                             ]
                           })json")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3316,6 +3350,40 @@
   }
 }
 
+TEST_F(RegistrationTest,
+       RegistrationBySubdomain_DoesNotInheritSubdomainQueryOrFragment) {
+  crypto::ScopedFakeUnexportableKeyProvider scoped_fake_key_provider;
+
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnForHostAndPath, "a.test", "/.well-known/device-bound-sessions",
+      base::BindRepeating(&ReturnWellKnown,
+                          R"json({
+                            "registering_origins": [ "https://subdomain.a.test:$1" ]
+                          })json")));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
+  ASSERT_TRUE(server_.Start());
+
+  // Registration URL includes query parameters and fragment.
+  GURL registration_url = server_.GetURL(
+      "subdomain.a.test", "/reg?evil=ATTACKER_CONTROLLED#fragment");
+
+  TestRegistrationCallback callback;
+  auto param = GetBasicParam(registration_url);
+  std::unique_ptr<RegistrationFetcher> fetcher =
+      RegistrationFetcher::CreateFetcher(
+          param, session_service(), unexportable_key_service(), context_.get(),
+          IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
+          SiteForCookies(),
+          /*net_log_source=*/std::nullopt,
+          /*original_request_initiator=*/std::nullopt,
+          unexportable_keys::BackgroundTaskPriority::kBestEffort);
+  fetcher->StartCreateTokenAndFetch(param, CreateAlgArray(),
+                                    callback.callback());
+  callback.WaitForCall();
+  callback.outcome().SessionForTesting();
+}
+
 TEST_F(RegistrationTest, RegistrationRedirectToSubdomain) {
   crypto::ScopedFakeUnexportableKeyProvider scoped_fake_key_provider;
   bool well_known_fetched = false;
@@ -3341,7 +3409,9 @@
         if (request.relative_url != "/dbsc") {
           return nullptr;
         }
-        return ReturnResponse(HTTP_OK, kBasicValidJson, request);
+        return ReturnResponse(
+            HTTP_OK, GetSubdomainValidJsonWithCustomPort(server_.port()),
+            request);
       }));
 
   // 3. Monitor well-known requests
@@ -3409,7 +3479,9 @@
         if (request.relative_url != "/dbsc") {
           return nullptr;
         }
-        return ReturnResponse(HTTP_OK, kBasicValidJson, request);
+        return ReturnResponse(
+            HTTP_OK, GetSubdomainValidJsonWithCustomPort(server_.port()),
+            request);
       }));
 
   // 3. .well-known Interceptor: Capture the outbound GET metadata for the
@@ -3496,6 +3568,37 @@
   EXPECT_EQ(session_or_error.SessionForTesting().unexportable_key_id(), key);
 }
 
+TEST_F(RegistrationTest, Federated_DoesNotInheritQueryOrFragment) {
+  crypto::ScopedFakeUnexportableKeyProvider scoped_fake_key_provider;
+
+  server_.RegisterRequestHandler(
+      base::BindRepeating(&ReturnForHostAndPath, "provider.a.test",
+                          "/.well-known/device-bound-sessions",
+                          base::BindRepeating(&ReturnWellKnown,
+                                              R"json({
+                                                "relying_origins": [ "https://rp.a.test:$1" ]
+                                              })json")));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnForHostAndPath, "rp.a.test", "/.well-known/device-bound-sessions",
+      base::BindRepeating(&ReturnWellKnown,
+                          R"json({
+                            "provider_origin": "https://provider.a.test:$1"
+                          })json")));
+  server_.RegisterRequestHandler(
+      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  ASSERT_TRUE(server_.Start());
+
+  UnexportableSigningKeyId key = CreateSigningKey();
+  auto param = RegistrationRequestParam::CreateForTesting(
+      server_.GetURL("rp.a.test", "/reg?rp_query=1#rp_frag"),
+      kSessionIdentifier, kChallenge,
+      /*authorization=*/std::nullopt);
+  GURL provider_url =
+      server_.GetURL("provider.a.test", "/path?provider_query=1#provider_frag");
+  auto session_or_error = FetchWithFederatedKey(param, key, provider_url);
+  EXPECT_EQ(session_or_error.SessionForTesting().unexportable_key_id(), key);
+}
+
 TEST_F(RegistrationTest, FederatedProviderHasProvider) {
   crypto::ScopedFakeUnexportableKeyProvider scoped_fake_key_provider;
 
diff --git a/net/device_bound_sessions/session_binding_utils_unittest.cc b/net/device_bound_sessions/session_binding_utils_unittest.cc
index 43bf7ce..0847aa2 100644
--- a/net/device_bound_sessions/session_binding_utils_unittest.cc
+++ b/net/device_bound_sessions/session_binding_utils_unittest.cc
@@ -370,4 +370,28 @@
             "cross-site");
 }
 
+TEST(SessionBindingUtilsTest, CreateWellKnownUrl) {
+  // 1. Standard HTTPS origin without explicit port.
+  EXPECT_EQ(
+      CreateWellKnownUrl(url::Origin::Create(GURL("https://example.com"))),
+      GURL("https://example.com/.well-known/device-bound-sessions"));
+
+  // 2. HTTPS origin with custom port.
+  EXPECT_EQ(
+      CreateWellKnownUrl(url::Origin::Create(GURL("https://example.com:8443"))),
+      GURL("https://example.com:8443/.well-known/device-bound-sessions"));
+
+  // 3. Origin created from URL with path, query, fragment, and credentials.
+  // Verify that path is replaced, and query, ref, and credentials are
+  // completely stripped.
+  GURL complex_url(
+      "https://user:pass@example.com:9443/some/"
+      "path?query=1&evil=true#fragment");
+  EXPECT_EQ(CreateWellKnownUrl(url::Origin::Create(complex_url)),
+            GURL("https://example.com:9443/.well-known/device-bound-sessions"));
+
+  // 4. Opaque origin returns empty GURL.
+  EXPECT_TRUE(CreateWellKnownUrl(url::Origin()).is_empty());
+}
+
 }  // namespace net::device_bound_sessions
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/net/device_bound_sessions/registration_fetcher_unittest.cc b/net/device_bound_sessions/registration_fetcher_unittest.cc
index 1b16dea..951fe0c 100644
--- a/net/device_bound_sessions/registration_fetcher_unittest.cc
+++ b/net/device_bound_sessions/registration_fetcher_unittest.cc
@@ -62,6 +62,7 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/functional/overload.h"
+#include "third_party/abseil-cpp/absl/strings/str_format.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -114,6 +115,32 @@
   }]
 })";
 
+std::string GetSubdomainValidJsonWithCustomPort(int port) {
+  constexpr char kSubdomainValidJson[] =
+      R"({
+  "session_identifier": "session_id",
+  "refresh_url": "/refresh",
+  "scope": {
+    "origin": "https://a.test:%v",
+    "include_site": true,
+    "scope_specification" : [
+      {
+        "type": "include",
+        "domain": "trusted.a.test",
+        "path": "/only_trusted_path"
+      }
+    ]
+  },
+  "credentials": [{
+    "type": "cookie",
+    "name": "auth_cookie",
+    "attributes": "Domain=a.test; Path=/; Secure; SameSite=None"
+  }]
+})";
+
+  return absl::StrFormat(kSubdomainValidJson, port);
+}
+
 constexpr char kSubdomainValidJsonIncludeSiteFalse[] =
     R"({
   "session_identifier": "session_id",
@@ -305,6 +332,13 @@
   return response;
 }
 
+std::unique_ptr<test_server::HttpResponse> ReturnSubdomainResponse(
+    const test_server::EmbeddedTestServer* server,
+    const test_server::HttpRequest& request) {
+  return ReturnResponse(
+      HTTP_OK, GetSubdomainValidJsonWithCustomPort(server->port()), request);
+}
+
 std::unique_ptr<test_server::HttpResponse> ReturnChallengeResponse(
     HttpStatusCode code,
     std::string_view challenge) {
@@ -3070,8 +3104,8 @@
                           R"json({
                             "registering_origins": [ "https://subdomain.a.test:$1" ]
                           })json")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3100,8 +3134,8 @@
   server_.RegisterRequestHandler(base::BindRepeating(
       &ReturnForHostAndPath, "a.test", "/.well-known/device-bound-sessions",
       base::BindRepeating(&ReturnResponse, HTTP_BAD_REQUEST, "")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3132,8 +3166,8 @@
   server_.RegisterRequestHandler(base::BindRepeating(
       &ReturnForHostAndPath, "a.test", "/.well-known/device-bound-sessions",
       base::BindRepeating(&ReturnResponse, HTTP_OK, "invalid JSON")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3165,8 +3199,8 @@
       &ReturnForHostAndPath, "a.test", "/.well-known/device-bound-sessions",
       base::BindRepeating(&ReturnResponse, HTTP_OK,
                           "{\"registering_origins\": [ 12345 ]}")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3200,8 +3234,8 @@
                           R"json({
                             "registering_origins": [ "https://subdomain.a.test:$1" ]
                           })json")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("not-allowed-subdomain.a.test", "/");
@@ -3268,8 +3302,8 @@
                               "https://other-subdomain.a.test:$1"
                             ]
                           })json")));
-  server_.RegisterRequestHandler(
-      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
   ASSERT_TRUE(server_.Start());
 
   GURL registration_url = server_.GetURL("subdomain.a.test", "/");
@@ -3316,6 +3350,40 @@
   }
 }
 
+TEST_F(RegistrationTest,
+       RegistrationBySubdomain_DoesNotInheritSubdomainQueryOrFragment) {
+  crypto::ScopedFakeUnexportableKeyProvider scoped_fake_key_provider;
+
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnForHostAndPath, "a.test", "/.well-known/device-bound-sessions",
+      base::BindRepeating(&ReturnWellKnown,
+                          R"json({
+                            "registering_origins": [ "https://subdomain.a.test:$1" ]
+                          })json")));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnSubdomainResponse, base::Unretained(&server_)));
+  ASSERT_TRUE(server_.Start());
+
+  // Registration URL includes query parameters and fragment.
+  GURL registration_url = server_.GetURL(
+      "subdomain.a.test", "/reg?evil=ATTACKER_CONTROLLED#fragment");
+
+  TestRegistrationCallback callback;
+  auto param = GetBasicParam(registration_url);
+  std::unique_ptr<RegistrationFetcher> fetcher =
+      RegistrationFetcher::CreateFetcher(
+          param, session_service(), unexportable_key_service(), context_.get(),
+          IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
+          SiteForCookies(),
+          /*net_log_source=*/std::nullopt,
+          /*original_request_initiator=*/std::nullopt,
+          unexportable_keys::BackgroundTaskPriority::kBestEffort);
+  fetcher->StartCreateTokenAndFetch(param, CreateAlgArray(),
+                                    callback.callback());
+  callback.WaitForCall();
+  callback.outcome().SessionForTesting();
+}
+
 TEST_F(RegistrationTest, RegistrationRedirectToSubdomain) {
   crypto::ScopedFakeUnexportableKeyProvider scoped_fake_key_provider;
   bool well_known_fetched = false;
@@ -3341,7 +3409,9 @@
         if (request.relative_url != "/dbsc") {
           return nullptr;
         }
-        return ReturnResponse(HTTP_OK, kBasicValidJson, request);
+        return ReturnResponse(
+            HTTP_OK, GetSubdomainValidJsonWithCustomPort(server_.port()),
+            request);
       }));
 
   // 3. Monitor well-known requests
@@ -3409,7 +3479,9 @@
         if (request.relative_url != "/dbsc") {
           return nullptr;
         }
-        return ReturnResponse(HTTP_OK, kBasicValidJson, request);
+        return ReturnResponse(
+            HTTP_OK, GetSubdomainValidJsonWithCustomPort(server_.port()),
+            request);
       }));
 
   // 3. .well-known Interceptor: Capture the outbound GET metadata for the
@@ -3496,6 +3568,37 @@
   EXPECT_EQ(session_or_error.SessionForTesting().unexportable_key_id(), key);
 }
 
+TEST_F(RegistrationTest, Federated_DoesNotInheritQueryOrFragment) {
+  crypto::ScopedFakeUnexportableKeyProvider scoped_fake_key_provider;
+
+  server_.RegisterRequestHandler(
+      base::BindRepeating(&ReturnForHostAndPath, "provider.a.test",
+                          "/.well-known/device-bound-sessions",
+                          base::BindRepeating(&ReturnWellKnown,
+                                              R"json({
+                                                "relying_origins": [ "https://rp.a.test:$1" ]
+                                              })json")));
+  server_.RegisterRequestHandler(base::BindRepeating(
+      &ReturnForHostAndPath, "rp.a.test", "/.well-known/device-bound-sessions",
+      base::BindRepeating(&ReturnWellKnown,
+                          R"json({
+                            "provider_origin": "https://provider.a.test:$1"
+                          })json")));
+  server_.RegisterRequestHandler(
+      base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson));
+  ASSERT_TRUE(server_.Start());
+
+  UnexportableSigningKeyId key = CreateSigningKey();
+  auto param = RegistrationRequestParam::CreateForTesting(
+      server_.GetURL("rp.a.test", "/reg?rp_query=1#rp_frag"),
+      kSessionIdentifier, kChallenge,
+      /*authorization=*/std::nullopt);
+  GURL provider_url =
+      server_.GetURL("provider.a.test", "/path?provider_query=1#provider_frag");
+  auto session_or_error = FetchWithFederatedKey(param, key, provider_url);
+  EXPECT_EQ(session_or_error.SessionForTesting().unexportable_key_id(), key);
+}
+
 TEST_F(RegistrationTest, FederatedProviderHasProvider) {
   crypto::ScopedFakeUnexportableKeyProvider scoped_fake_key_provider;
diff --git a/net/device_bound_sessions/session_binding_utils_unittest.cc b/net/device_bound_sessions/session_binding_utils_unittest.cc
index 43bf7ce..0847aa2 100644
--- a/net/device_bound_sessions/session_binding_utils_unittest.cc
+++ b/net/device_bound_sessions/session_binding_utils_unittest.cc
@@ -370,4 +370,28 @@
             "cross-site");
 }
 
+TEST(SessionBindingUtilsTest, CreateWellKnownUrl) {
+  // 1. Standard HTTPS origin without explicit port.
+  EXPECT_EQ(
+      CreateWellKnownUrl(url::Origin::Create(GURL("https://example.com"))),
+      GURL("https://example.com/.well-known/device-bound-sessions"));
+
+  // 2. HTTPS origin with custom port.
+  EXPECT_EQ(
+      CreateWellKnownUrl(url::Origin::Create(GURL("https://example.com:8443"))),
+      GURL("https://example.com:8443/.well-known/device-bound-sessions"));
+
+  // 3. Origin created from URL with path, query, fragment, and credentials.
+  // Verify that path is replaced, and query, ref, and credentials are
+  // completely stripped.
+  GURL complex_url(
+      "https://user:pass@example.com:9443/some/"
+      "path?query=1&evil=true#fragment");
+  EXPECT_EQ(CreateWellKnownUrl(url::Origin::Create(complex_url)),
+            GURL("https://example.com:9443/.well-known/device-bound-sessions"));
+
+  // 4. Opaque origin returns empty GURL.
+  EXPECT_TRUE(CreateWellKnownUrl(url::Origin()).is_empty());
+}
+
 }  // namespace net::device_bound_sessions
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.