Add credentials to same-origin Report uploads

When the reporting endpoint is same-origin with the page generating
reports, the reporting API specifies that credentials should be included
with the report delivery.

This change enables that behaviour for V1 reporting endpoints only --
any endpoints configured with the Report-To header are unaffected. This
CL corrects the way that we distinguish V0 from V1 endpoints at delivery
time, which also corrects the metrics collection for delivered reports,
and adds a test for a case which was previously missed.

Bug: 1163645
Change-Id: I8fcd934b3026b57374e7e7df31c9af9a8d93962d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3169200
Reviewed-by: Matt Menke <[email protected]>
Reviewed-by: Maksim Orlovich <[email protected]>
Reviewed-by: Ken Buchanan <[email protected]>
Reviewed-by: Mike West <[email protected]>
Reviewed-by: Christian Dullweber <[email protected]>
Commit-Queue: Ian Clelland <[email protected]>
Cr-Commit-Position: refs/heads/main@{#929239}
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index c1f4bd10..deac4e87 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -125,6 +125,7 @@
 #include "content/public/test/mock_download_manager.h"
 #include "content/public/test/test_utils.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "net/base/isolation_info.h"
 #include "net/base/network_isolation_key.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_access_result.h"
@@ -904,7 +905,7 @@
   void SetDocumentReportingEndpoints(
       const base::UnguessableToken& reporting_source,
       const url::Origin& origin,
-      const net::NetworkIsolationKey& network_isolation_key,
+      const net::IsolationInfo& isolation_info,
       const base::flat_map<std::string, std::string>& endpoints) override {
     NOTREACHED();
   }
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 5835e6e..6520b9b 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -3560,8 +3560,8 @@
   // reporting cache.
   if (!reporting_endpoints_.empty()) {
     GetStoragePartition()->GetNetworkContext()->SetDocumentReportingEndpoints(
-        GetReportingSource(), params.origin,
-        isolation_info_.network_isolation_key(), reporting_endpoints_);
+        GetReportingSource(), params.origin, isolation_info_,
+        reporting_endpoints_);
   }
 
   // When the frame hosts a different document, its state must be replicated
diff --git a/net/reporting/reporting_cache.h b/net/reporting/reporting_cache.h
index 39b4421..0405e1b 100644
--- a/net/reporting/reporting_cache.h
+++ b/net/reporting/reporting_cache.h
@@ -27,6 +27,7 @@
 namespace net {
 
 class ReportingContext;
+class IsolationInfo;
 
 // The cache holds undelivered reports and clients (per-origin endpoint
 // configurations) in memory. (It is not responsible for persisting them.)
@@ -173,13 +174,11 @@
   // received Reporting-Endpoints header.
   // |reporting_source| is the token identifying the document or worker with
   // which this header was received, and may not be empty.
-  // |origin| is the origin of the document or worker represented by
-  // |reporting_source|, and |network_isolation_key| is the appropriate NIK for
-  // that source. These two parameters are currently needed because the new
-  // Reporting API currently shares the cache, delivery agent and
-  // ReportingEndpoint struct with the old API.
+  // |isolation_info| is the appropriate network isolation info struct for that
+  // source, and is used for determining credentials to send with reports.
   virtual void OnParsedReportingEndpointsHeader(
       const base::UnguessableToken& reporting_source,
+      const IsolationInfo& isolation_info,
       std::vector<ReportingEndpoint> parsed_header) = 0;
 
   // Gets all the origins of clients in the cache.
@@ -304,13 +303,23 @@
                                      int priority,
                                      int weight) = 0;
 
-  // Sets a V1 named endpoint with the given key for |reporting_source|,
+  // Sets a V1 named endpoint with the given key for `reporting_source`,
   // bypassing header parsing. This method inserts a single endpoint while
-  // leaving the existing configuration for that source intact.
+  // leaving the existing configuration for that source intact. If any
+  // endpoints already exist for this source, then `isolation_info` must
+  // match the value that was previously associated with it.
   virtual void SetV1EndpointForTesting(
       const ReportingEndpointGroupKey& group_key,
       const base::UnguessableToken& reporting_source,
+      const IsolationInfo& isolation_info,
       const GURL& url) = 0;
+
+  // Gets the isolation info associated with `reporting_source`, used when
+  // determining which credentials to send for a given report. If
+  // `reporting_source` is nullopt, as when a report is being delivered to a V0
+  // reporting endpoint group, this always will return an empty site.
+  virtual IsolationInfo GetIsolationInfoForEndpoint(
+      const ReportingEndpoint& endpoint) const = 0;
 };
 
 // Persistent storage for Reporting reports and clients.
diff --git a/net/reporting/reporting_cache_impl.cc b/net/reporting/reporting_cache_impl.cc
index 909103268..32a80c8c 100644
--- a/net/reporting/reporting_cache_impl.cc
+++ b/net/reporting/reporting_cache_impl.cc
@@ -392,16 +392,21 @@
                report->status != ReportingReport::Status::SUCCESS;
       }));
   document_endpoints_.erase(reporting_source);
+  isolation_info_.erase(reporting_source);
   expired_sources_.erase(reporting_source);
   context_->NotifyEndpointsUpdated();
 }
 
 void ReportingCacheImpl::OnParsedReportingEndpointsHeader(
     const base::UnguessableToken& reporting_source,
+    const IsolationInfo& isolation_info,
     std::vector<ReportingEndpoint> endpoints) {
   DCHECK(!reporting_source.is_empty());
   DCHECK(!endpoints.empty());
+  DCHECK_EQ(0u, document_endpoints_.count(reporting_source));
+  DCHECK_EQ(0u, isolation_info_.count(reporting_source));
   document_endpoints_.insert({reporting_source, std::move(endpoints)});
+  isolation_info_.insert({reporting_source, isolation_info});
   context_->NotifyEndpointsUpdated();
 }
 
@@ -787,10 +792,14 @@
 void ReportingCacheImpl::SetV1EndpointForTesting(
     const ReportingEndpointGroupKey& group_key,
     const base::UnguessableToken& reporting_source,
+    const IsolationInfo& isolation_info,
     const GURL& url) {
   DCHECK(!reporting_source.is_empty());
   DCHECK(group_key.IsDocumentEndpoint());
   DCHECK_EQ(reporting_source, group_key.reporting_source.value());
+  DCHECK_EQ(group_key.network_isolation_key,
+            isolation_info.network_isolation_key());
+
   ReportingEndpoint::EndpointInfo info;
   info.url = url;
   ReportingEndpoint new_endpoint(group_key, info);
@@ -804,6 +813,15 @@
   } else {
     document_endpoints_.insert({reporting_source, {std::move(new_endpoint)}});
   }
+  // If this is the first time we've used this reporting_source, then add the
+  // isolation info. Otherwise, ensure that it is the same as what was used
+  // previously.
+  if (isolation_info_.count(reporting_source) == 0) {
+    isolation_info_.insert({reporting_source, isolation_info});
+  } else {
+    DCHECK(isolation_info_.at(reporting_source)
+               .IsEqualForTesting(isolation_info));  // IN-TEST
+  }
   context_->NotifyEndpointsUpdated();
 }
 
@@ -865,6 +883,20 @@
   context_->NotifyCachedClientsUpdated();
 }
 
+IsolationInfo ReportingCacheImpl::GetIsolationInfoForEndpoint(
+    const ReportingEndpoint& endpoint) const {
+  // V0 endpoint groups do not support credentials.
+  if (!endpoint.group_key.reporting_source.has_value()) {
+    return IsolationInfo::CreatePartial(
+        IsolationInfo::RequestType::kOther,
+        endpoint.group_key.network_isolation_key);
+  }
+  const auto it =
+      isolation_info_.find(endpoint.group_key.reporting_source.value());
+  DCHECK(it != isolation_info_.end());
+  return it->second;
+}
+
 ReportingCacheImpl::Client::Client(
     const NetworkIsolationKey& network_isolation_key,
     const url::Origin& origin)
diff --git a/net/reporting/reporting_cache_impl.h b/net/reporting/reporting_cache_impl.h
index ee886b9..20897fd 100644
--- a/net/reporting/reporting_cache_impl.h
+++ b/net/reporting/reporting_cache_impl.h
@@ -20,6 +20,7 @@
 #include "base/time/time.h"
 #include "base/unguessable_token.h"
 #include "base/values.h"
+#include "net/base/isolation_info.h"
 #include "net/reporting/reporting_cache.h"
 #include "net/reporting/reporting_context.h"
 #include "net/reporting/reporting_endpoint.h"
@@ -85,6 +86,7 @@
       std::vector<ReportingEndpointGroup> parsed_header) override;
   void OnParsedReportingEndpointsHeader(
       const base::UnguessableToken& reporting_source,
+      const IsolationInfo& isolation_info,
       std::vector<ReportingEndpoint> parsed_header) override;
   std::set<url::Origin> GetAllOrigins() const override;
   void RemoveClient(const NetworkIsolationKey& network_isolation_key,
@@ -126,7 +128,10 @@
                              int weight) override;
   void SetV1EndpointForTesting(const ReportingEndpointGroupKey& group_key,
                                const base::UnguessableToken& reporting_source,
+                               const IsolationInfo& isolation_info,
                                const GURL& url) override;
+  IsolationInfo GetIsolationInfoForEndpoint(
+      const ReportingEndpoint& endpoint) const override;
 
  private:
   // Represents the entire Report-To configuration for a (NIK, origin) pair.
@@ -366,7 +371,7 @@
   std::multimap<GURL, EndpointMap::iterator> endpoint_its_by_url_;
 
   // Reporting API V1 Cache:
-  // The |document_endpoints_| member holds endpoint configuration for the V1
+  // The `document_endpoints_` member holds endpoint configuration for the V1
   // API, configured through the Reporting-Endpoints HTTP header. These
   // endpoints are strongly associated with the resource which configured them,
   // and are only used for document reports.
@@ -376,9 +381,14 @@
   std::map<base::UnguessableToken, std::vector<ReportingEndpoint>>
       document_endpoints_;
 
+  // Isolation info for each reporting source. Used for determining credentials
+  // to send when delivering reports. This contains only V1 document endpoints.
+  std::map<base::UnguessableToken, IsolationInfo> isolation_info_;
+
   // Reporting source tokens representing sources which have been destroyed.
-  // The configuration in `document_endpoints_` for these sources can be
-  // removed once all outstanding reports are delivered (or expired).
+  // The configuration in `document_endpoints_` and `isolation_info_` for these
+  // sources can be removed once all outstanding reports are delivered (or
+  // expired).
   base::flat_set<base::UnguessableToken> expired_sources_;
 
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/net/reporting/reporting_cache_unittest.cc b/net/reporting/reporting_cache_unittest.cc
index f51b84c..88d1984 100644
--- a/net/reporting/reporting_cache_unittest.cc
+++ b/net/reporting/reporting_cache_unittest.cc
@@ -189,6 +189,16 @@
   const NetworkIsolationKey kNik_;
   const NetworkIsolationKey kOtherNik_ =
       NetworkIsolationKey(SchemefulSite(kOrigin1_), SchemefulSite(kOrigin2_));
+  const IsolationInfo kIsolationInfo1_ =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther,
+                            kOrigin1_,
+                            kOrigin1_,
+                            SiteForCookies::FromOrigin(kOrigin1_));
+  const IsolationInfo kIsolationInfo2_ =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther,
+                            kOrigin2_,
+                            kOrigin2_,
+                            SiteForCookies::FromOrigin(kOrigin2_));
   const GURL kEndpoint1_ = GURL("https://endpoint1/");
   const GURL kEndpoint2_ = GURL("https://endpoint2/");
   const GURL kEndpoint3_ = GURL("https://endpoint3/");
@@ -957,15 +967,23 @@
       base::UnguessableToken::Create();
   LoadReportingClients();
 
+  NetworkIsolationKey network_isolation_key_1 =
+      kIsolationInfo1_.network_isolation_key();
+  NetworkIsolationKey network_isolation_key_2 =
+      kIsolationInfo2_.network_isolation_key();
+
   cache()->SetV1EndpointForTesting(
-      ReportingEndpointGroupKey(kNik_, *kReportingSource_, kOrigin1_, kGroup1_),
-      *kReportingSource_, kUrl1_);
+      ReportingEndpointGroupKey(network_isolation_key_1, *kReportingSource_,
+                                kOrigin1_, kGroup1_),
+      *kReportingSource_, kIsolationInfo1_, kUrl1_);
   cache()->SetV1EndpointForTesting(
-      ReportingEndpointGroupKey(kNik_, *kReportingSource_, kOrigin1_, kGroup2_),
-      *kReportingSource_, kUrl2_);
+      ReportingEndpointGroupKey(network_isolation_key_1, *kReportingSource_,
+                                kOrigin1_, kGroup2_),
+      *kReportingSource_, kIsolationInfo1_, kUrl2_);
   cache()->SetV1EndpointForTesting(
-      ReportingEndpointGroupKey(kNik_, reporting_source_2, kOrigin2_, kGroup1_),
-      reporting_source_2, kUrl2_);
+      ReportingEndpointGroupKey(network_isolation_key_2, reporting_source_2,
+                                kOrigin2_, kGroup1_),
+      reporting_source_2, kIsolationInfo2_, kUrl2_);
 
   EXPECT_EQ(2u, cache()->GetReportingSourceCountForTesting());
   EXPECT_TRUE(cache()->GetV1EndpointForTesting(*kReportingSource_, kGroup1_));
@@ -1083,18 +1101,26 @@
   const base::UnguessableToken reporting_source_2 =
       base::UnguessableToken::Create();
 
+  NetworkIsolationKey network_isolation_key =
+      kIsolationInfo1_.network_isolation_key();
   const ReportingEndpointGroupKey document_group_key_1 =
-      ReportingEndpointGroupKey(kNik_, reporting_source_1, kOrigin1_, kGroup1_);
+      ReportingEndpointGroupKey(network_isolation_key, reporting_source_1,
+                                kOrigin1_, kGroup1_);
   const ReportingEndpointGroupKey document_group_key_2 =
-      ReportingEndpointGroupKey(kNik_, reporting_source_1, kOrigin1_, kGroup2_);
+      ReportingEndpointGroupKey(network_isolation_key, reporting_source_1,
+                                kOrigin1_, kGroup2_);
   const ReportingEndpointGroupKey document_group_key_3 =
-      ReportingEndpointGroupKey(kNik_, reporting_source_2, kOrigin1_, kGroup1_);
+      ReportingEndpointGroupKey(network_isolation_key, reporting_source_2,
+                                kOrigin1_, kGroup1_);
 
-  SetV1EndpointInCache(document_group_key_1, reporting_source_1, kEndpoint1_);
-  SetV1EndpointInCache(document_group_key_2, reporting_source_1, kEndpoint2_);
-  SetV1EndpointInCache(document_group_key_3, reporting_source_2, kEndpoint1_);
-  const ReportingEndpointGroupKey kReportGroupKey =
-      ReportingEndpointGroupKey(kNik_, reporting_source_1, kOrigin1_, kGroup1_);
+  SetV1EndpointInCache(document_group_key_1, reporting_source_1,
+                       kIsolationInfo1_, kEndpoint1_);
+  SetV1EndpointInCache(document_group_key_2, reporting_source_1,
+                       kIsolationInfo1_, kEndpoint2_);
+  SetV1EndpointInCache(document_group_key_3, reporting_source_2,
+                       kIsolationInfo1_, kEndpoint1_);
+  const ReportingEndpointGroupKey kReportGroupKey = ReportingEndpointGroupKey(
+      network_isolation_key, reporting_source_1, kOrigin1_, kGroup1_);
   std::vector<ReportingEndpoint> candidate_endpoints =
       cache()->GetCandidateEndpointsForDelivery(kReportGroupKey);
   ASSERT_EQ(1u, candidate_endpoints.size());
@@ -1107,12 +1133,17 @@
   const base::UnguessableToken reporting_source =
       base::UnguessableToken::Create();
 
-  const ReportingEndpointGroupKey kDocumentGroupKey =
-      ReportingEndpointGroupKey(kNik_, reporting_source, kOrigin1_, kGroup1_);
+  NetworkIsolationKey network_isolation_key =
+      kIsolationInfo1_.network_isolation_key();
 
-  SetV1EndpointInCache(kDocumentGroupKey, reporting_source, kEndpoint1_);
+  const ReportingEndpointGroupKey kDocumentGroupKey = ReportingEndpointGroupKey(
+      network_isolation_key, reporting_source, kOrigin1_, kGroup1_);
+
+  SetV1EndpointInCache(kDocumentGroupKey, reporting_source, kIsolationInfo1_,
+                       kEndpoint1_);
   const ReportingEndpointGroupKey kNetworkReportGroupKey =
-      ReportingEndpointGroupKey(kNik_, absl::nullopt, kOrigin1_, kGroup1_);
+      ReportingEndpointGroupKey(network_isolation_key, absl::nullopt, kOrigin1_,
+                                kGroup1_);
   std::vector<ReportingEndpoint> candidate_endpoints =
       cache()->GetCandidateEndpointsForDelivery(kNetworkReportGroupKey);
   ASSERT_EQ(0u, candidate_endpoints.size());
@@ -1124,12 +1155,17 @@
   const base::UnguessableToken reporting_source =
       base::UnguessableToken::Create();
 
-  const ReportingEndpointGroupKey kDocumentGroupKey =
-      ReportingEndpointGroupKey(kNik_, reporting_source, kOrigin1_, kGroup1_);
+  NetworkIsolationKey network_isolation_key =
+      kIsolationInfo1_.network_isolation_key();
 
-  SetV1EndpointInCache(kDocumentGroupKey, reporting_source, kEndpoint1_);
+  const ReportingEndpointGroupKey kDocumentGroupKey = ReportingEndpointGroupKey(
+      network_isolation_key, reporting_source, kOrigin1_, kGroup1_);
+
+  SetV1EndpointInCache(kDocumentGroupKey, reporting_source, kIsolationInfo1_,
+                       kEndpoint1_);
   const ReportingEndpointGroupKey kOtherGroupKey = ReportingEndpointGroupKey(
-      kNik_, base::UnguessableToken::Create(), kOrigin1_, kGroup1_);
+      network_isolation_key, base::UnguessableToken::Create(), kOrigin1_,
+      kGroup1_);
   std::vector<ReportingEndpoint> candidate_endpoints =
       cache()->GetCandidateEndpointsForDelivery(kOtherGroupKey);
   ASSERT_EQ(0u, candidate_endpoints.size());
@@ -1142,41 +1178,60 @@
 TEST_P(ReportingCacheTest, GetMixedCandidateEndpointsForDelivery) {
   LoadReportingClients();
 
-  // Set up V0 endpoint groups for this origin
-  ASSERT_TRUE(SetEndpointInCache(kGroupKey11_, kEndpoint1_, kExpires1_));
-  ASSERT_TRUE(SetEndpointInCache(kGroupKey11_, kEndpoint2_, kExpires1_));
-  ASSERT_TRUE(SetEndpointInCache(kGroupKey12_, kEndpoint2_, kExpires1_));
-  ASSERT_TRUE(SetEndpointInCache(kGroupKey21_, kEndpoint1_, kExpires1_));
+  // This test relies on proper NIKs being used, so set those up, and endpoint
+  // group keys to go with them.
+  NetworkIsolationKey network_isolation_key1 =
+      kIsolationInfo1_.network_isolation_key();
+  NetworkIsolationKey network_isolation_key2 =
+      kIsolationInfo2_.network_isolation_key();
+  ReportingEndpointGroupKey group_key_11 =
+      ReportingEndpointGroupKey(network_isolation_key1, kOrigin1_, kGroup1_);
+  ReportingEndpointGroupKey group_key_12 =
+      ReportingEndpointGroupKey(network_isolation_key1, kOrigin1_, kGroup2_);
+  ReportingEndpointGroupKey group_key_21 =
+      ReportingEndpointGroupKey(network_isolation_key2, kOrigin2_, kGroup1_);
 
-  // Set up a V1 endpoint for a document at the same origin
+  // Set up V0 endpoint groups for this origin.
+  ASSERT_TRUE(SetEndpointInCache(group_key_11, kEndpoint1_, kExpires1_));
+  ASSERT_TRUE(SetEndpointInCache(group_key_11, kEndpoint2_, kExpires1_));
+  ASSERT_TRUE(SetEndpointInCache(group_key_12, kEndpoint2_, kExpires1_));
+  ASSERT_TRUE(SetEndpointInCache(group_key_21, kEndpoint1_, kExpires1_));
+
+  // Set up a V1 endpoint for a document at the same origin.
+  NetworkIsolationKey network_isolation_key =
+      kIsolationInfo1_.network_isolation_key();
   const base::UnguessableToken reporting_source =
       base::UnguessableToken::Create();
   const ReportingEndpointGroupKey document_group_key =
-      ReportingEndpointGroupKey(kNik_, reporting_source, kOrigin1_, kGroup1_);
-  SetV1EndpointInCache(document_group_key, reporting_source, kEndpoint1_);
+      ReportingEndpointGroupKey(network_isolation_key1, reporting_source,
+                                kOrigin1_, kGroup1_);
+  SetV1EndpointInCache(document_group_key, reporting_source, kIsolationInfo1_,
+                       kEndpoint1_);
 
   // This group key will match both the V1 endpoint, and two V0 endpoints. Only
   // the V1 endpoint should be returned.
   std::vector<ReportingEndpoint> candidate_endpoints =
       cache()->GetCandidateEndpointsForDelivery(ReportingEndpointGroupKey(
-          kNik_, reporting_source, kOrigin1_, kGroup1_));
+          network_isolation_key1, reporting_source, kOrigin1_, kGroup1_));
   ASSERT_EQ(1u, candidate_endpoints.size());
   EXPECT_EQ(document_group_key, candidate_endpoints[0].group_key);
 
   // This group key has no reporting source, so only V0 endpoints can be
   // returned.
-  candidate_endpoints = cache()->GetCandidateEndpointsForDelivery(
-      ReportingEndpointGroupKey(kNik_, absl::nullopt, kOrigin1_, kGroup1_));
+  candidate_endpoints =
+      cache()->GetCandidateEndpointsForDelivery(ReportingEndpointGroupKey(
+          network_isolation_key1, absl::nullopt, kOrigin1_, kGroup1_));
   ASSERT_EQ(2u, candidate_endpoints.size());
-  EXPECT_EQ(kGroupKey11_, candidate_endpoints[0].group_key);
-  EXPECT_EQ(kGroupKey11_, candidate_endpoints[1].group_key);
+  EXPECT_EQ(group_key_11, candidate_endpoints[0].group_key);
+  EXPECT_EQ(group_key_11, candidate_endpoints[1].group_key);
 
   // This group key has a reporting source, but no matching V1 endpoints have
   // been configured, so we should fall back to the V0 endpoints.
-  candidate_endpoints = cache()->GetCandidateEndpointsForDelivery(
-      ReportingEndpointGroupKey(kNik_, reporting_source, kOrigin1_, kGroup2_));
+  candidate_endpoints =
+      cache()->GetCandidateEndpointsForDelivery(ReportingEndpointGroupKey(
+          network_isolation_key1, reporting_source, kOrigin1_, kGroup2_));
   ASSERT_EQ(1u, candidate_endpoints.size());
-  EXPECT_EQ(kGroupKey12_, candidate_endpoints[0].group_key);
+  EXPECT_EQ(group_key_12, candidate_endpoints[0].group_key);
 }
 
 TEST_P(ReportingCacheTest, GetCandidateEndpointsDifferentNik) {
@@ -1864,6 +1919,48 @@
   EXPECT_TRUE(EndpointExistsInCache(group4, kEndpoint1_));
 }
 
+TEST_P(ReportingCacheTest, GetIsolationInfoForEndpoint) {
+  LoadReportingClients();
+
+  NetworkIsolationKey network_isolation_key1 =
+      kIsolationInfo1_.network_isolation_key();
+
+  // Set up a V1 endpoint for this origin.
+  cache()->SetV1EndpointForTesting(
+      ReportingEndpointGroupKey(network_isolation_key1, *kReportingSource_,
+                                kOrigin1_, kGroup1_),
+      *kReportingSource_, kIsolationInfo1_, kUrl1_);
+
+  // Set up a V0 endpoint group for this origin.
+  ReportingEndpointGroupKey group_key_11 =
+      ReportingEndpointGroupKey(network_isolation_key1, kOrigin1_, kGroup1_);
+  ASSERT_TRUE(SetEndpointInCache(group_key_11, kEndpoint1_, kExpires1_));
+
+  // For a V1 endpoint, ensure that the isolation info matches exactly what was
+  // passed in.
+  ReportingEndpoint endpoint =
+      cache()->GetV1EndpointForTesting(*kReportingSource_, kGroup1_);
+  EXPECT_TRUE(endpoint);
+  IsolationInfo isolation_info_for_document =
+      cache()->GetIsolationInfoForEndpoint(endpoint);
+  EXPECT_TRUE(isolation_info_for_document.IsEqualForTesting(kIsolationInfo1_));
+  EXPECT_EQ(isolation_info_for_document.request_type(),
+            IsolationInfo::RequestType::kOther);
+
+  // For a V0 endpoint, ensure that site_for_cookies is null and that the NIK
+  // matches the cached endpoint.
+  ReportingEndpoint network_endpoint =
+      cache()->GetEndpointForTesting(group_key_11, kEndpoint1_);
+  EXPECT_TRUE(network_endpoint);
+  IsolationInfo isolation_info_for_network =
+      cache()->GetIsolationInfoForEndpoint(network_endpoint);
+  EXPECT_EQ(isolation_info_for_network.request_type(),
+            IsolationInfo::RequestType::kOther);
+  EXPECT_EQ(isolation_info_for_network.network_isolation_key(),
+            network_endpoint.group_key.network_isolation_key);
+  EXPECT_TRUE(isolation_info_for_network.site_for_cookies().IsNull());
+}
+
 INSTANTIATE_TEST_SUITE_P(ReportingCacheStoreTest,
                          ReportingCacheTest,
                          testing::Bool());
diff --git a/net/reporting/reporting_delivery_agent.cc b/net/reporting/reporting_delivery_agent.cc
index 4161ab9..6b92f1f 100644
--- a/net/reporting/reporting_delivery_agent.cc
+++ b/net/reporting/reporting_delivery_agent.cc
@@ -19,6 +19,7 @@
 #include "base/time/tick_clock.h"
 #include "base/timer/timer.h"
 #include "base/values.h"
+#include "net/base/isolation_info.h"
 #include "net/base/network_isolation_key.h"
 #include "net/base/url_util.h"
 #include "net/reporting/reporting_cache.h"
@@ -82,11 +83,11 @@
   // report origin and reporting source are the same, and they all get assigned
   // to the same endpoint URL.
   struct Target {
-    Target(const NetworkIsolationKey& network_isolation_key,
+    Target(const IsolationInfo& isolation_info,
            const url::Origin& origin,
            const GURL& endpoint_url,
            const absl::optional<base::UnguessableToken> reporting_source)
-        : network_isolation_key(network_isolation_key),
+        : isolation_info(isolation_info),
           origin(origin),
           endpoint_url(endpoint_url),
           reporting_source(reporting_source) {}
@@ -94,13 +95,16 @@
     ~Target() = default;
 
     bool operator<(const Target& other) const {
-      return std::tie(network_isolation_key, origin, endpoint_url,
-                      reporting_source) <
-             std::tie(other.network_isolation_key, other.origin,
-                      other.endpoint_url, other.reporting_source);
+      // Note that sorting by NIK here is required for V0 reports; V1 reports
+      // should not need this (but it doesn't hurt). We can remove that as a
+      // comparison key when V0 reporting endpoints are removed.
+      return std::tie(isolation_info.network_isolation_key(), origin,
+                      endpoint_url, reporting_source) <
+             std::tie(other.isolation_info.network_isolation_key(),
+                      other.origin, other.endpoint_url, other.reporting_source);
     }
 
-    NetworkIsolationKey network_isolation_key;
+    IsolationInfo isolation_info;
     url::Origin origin;
     GURL endpoint_url;
     absl::optional<base::UnguessableToken> reporting_source;
@@ -161,7 +165,7 @@
   }
 
   const NetworkIsolationKey& network_isolation_key() const {
-    return target_.network_isolation_key;
+    return target_.isolation_info.network_isolation_key();
   }
   const GURL& endpoint_url() const { return target_.endpoint_url; }
   const ReportList& reports() const { return reports_; }
@@ -299,10 +303,13 @@
 
       pending_groups_.insert(report_group_key);
 
+      IsolationInfo isolation_info =
+          cache()->GetIsolationInfoForEndpoint(endpoint);
+
       // Add the reports to the appropriate delivery.
-      Delivery::Target target(report_group_key.network_isolation_key,
-                              report_group_key.origin, endpoint.info.url,
-                              report_group_key.reporting_source);
+      Delivery::Target target(isolation_info, report_group_key.origin,
+                              endpoint.info.url,
+                              endpoint.group_key.reporting_source);
       auto delivery_it = deliveries.find(target);
       if (delivery_it == deliveries.end()) {
         bool inserted;
@@ -335,8 +342,9 @@
 
       // TODO: Calculate actual max depth.
       uploader()->StartUpload(
-          target.origin, target.endpoint_url, target.network_isolation_key,
+          target.origin, target.endpoint_url, target.isolation_info,
           upload_data, max_depth,
+          /*eligible_for_credentials=*/target.reporting_source.has_value(),
           base::BindOnce(&ReportingDeliveryAgentImpl::OnUploadComplete,
                          weak_factory_.GetWeakPtr(), std::move(delivery)));
     }
diff --git a/net/reporting/reporting_delivery_agent_unittest.cc b/net/reporting/reporting_delivery_agent_unittest.cc
index 6f734a1f..1000959 100644
--- a/net/reporting/reporting_delivery_agent_unittest.cc
+++ b/net/reporting/reporting_delivery_agent_unittest.cc
@@ -17,6 +17,7 @@
 #include "base/values.h"
 #include "net/base/backoff_entry.h"
 #include "net/base/features.h"
+#include "net/base/isolation_info.h"
 #include "net/base/network_isolation_key.h"
 #include "net/base/schemeful_site.h"
 #include "net/reporting/reporting_cache.h"
@@ -88,10 +89,10 @@
   // has matching reporting_source.
   void UploadFirstDocumentReportAndStartTimer() {
     ReportingEndpointGroupKey dummy_group(
-        NetworkIsolationKey(), kDocumentReportingSource_,
+        kNik_, kDocumentReportingSource_,
         url::Origin::Create(GURL("https://dummy.test")), "dummy");
     SetV1EndpointInCache(dummy_group, kDocumentReportingSource_,
-                         GURL("https://dummy.test/upload"));
+                         kIsolationInfo_, GURL("https://dummy.test/upload"));
     AddReport(kDocumentReportingSource_, dummy_group.network_isolation_key,
               dummy_group.origin.GetURL(), dummy_group.group_name);
 
@@ -123,6 +124,16 @@
   const NetworkIsolationKey kOtherNik_ =
       NetworkIsolationKey(SchemefulSite(kOtherOrigin_),
                           SchemefulSite(kOtherOrigin_));
+  const IsolationInfo kIsolationInfo_ =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther,
+                            kOrigin_,
+                            kOrigin_,
+                            SiteForCookies::FromOrigin(kOrigin_));
+  const IsolationInfo kOtherIsolationInfo_ =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther,
+                            kOtherOrigin_,
+                            kOtherOrigin_,
+                            SiteForCookies::FromOrigin(kOtherOrigin_));
   const GURL kEndpoint_ = GURL("https://endpoint/");
   const std::string kUserAgent_ = "Mozilla/1.0";
   const std::string kGroup_ = "group";
@@ -186,11 +197,33 @@
   // TODO(dcreager): Check that BackoffEntry was informed of success.
 }
 
+TEST_F(ReportingDeliveryAgentTest, ReportToHeaderCountedCorrectly) {
+  base::HistogramTester histograms;
+
+  // Set an endpoint with no reporting source (as if configured with the
+  // Report-To header).
+  ASSERT_TRUE(SetEndpointInCache(kGroupKey_, kEndpoint_, kExpires_));
+
+  // Add and upload a report with an associated source.
+  AddReport(kDocumentReportingSource_, kNik_, kUrl_, kGroup_);
+  pending_uploads()[0]->Complete(ReportingUploader::Outcome::SUCCESS);
+
+  // Successful upload should count this as a Report-To delivery, even though
+  // the report itself had a reporting source.
+  histograms.ExpectBucketCount(
+      kReportingUploadHeaderTypeHistogram,
+      ReportingDeliveryAgent::ReportingUploadHeaderType::kReportTo, 1);
+  histograms.ExpectBucketCount(
+      kReportingUploadHeaderTypeHistogram,
+      ReportingDeliveryAgent::ReportingUploadHeaderType::kReportingEndpoints,
+      0);
+}
+
 TEST_F(ReportingDeliveryAgentTest, SuccessfulImmediateUploadDocumentReport) {
   base::HistogramTester histograms;
 
   SetV1EndpointInCache(kDocumentGroupKey_, kDocumentReportingSource_,
-                       kEndpoint_);
+                       kIsolationInfo_, kEndpoint_);
   AddReport(kDocumentReportingSource_, kNik_, kUrl_, kGroup_);
 
   // Upload is automatically started when cache is modified.
@@ -242,7 +275,7 @@
   base::HistogramTester histograms;
 
   SetV1EndpointInCache(kDocumentGroupKey_, kDocumentReportingSource_,
-                       kEndpoint_);
+                       kIsolationInfo_, kEndpoint_);
   AddReport(kDocumentReportingSource_, kNik_, kUrl_, kGroup_);
   AddReport(kDocumentReportingSource_, kNik_, kUrl_, kGroup_);
 
@@ -767,6 +800,16 @@
   const base::UnguessableToken kReportingSource3 =
       base::UnguessableToken::Create();
 
+  const IsolationInfo kIsolationInfo1 =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther, kOrigin_,
+                            kOrigin_, SiteForCookies::FromOrigin(kOrigin_));
+  const IsolationInfo kIsolationInfo2 =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther, kOrigin_,
+                            kOrigin_, SiteForCookies::FromOrigin(kOrigin_));
+  const IsolationInfo kIsolationInfo3 = IsolationInfo::Create(
+      IsolationInfo::RequestType::kOther, kOtherOrigin_, kOtherOrigin_,
+      SiteForCookies::FromOrigin(kOtherOrigin_));
+
   // Set up identical endpoint configuration for kReportingSource1 and
   // kReportingSource2. kReportingSource3 is independent.
   const ReportingEndpointGroupKey kGroup1Key1(kNik_, kReportingSource1,
@@ -780,11 +823,12 @@
   const ReportingEndpointGroupKey kOtherGroupKey(kOtherNik_, kReportingSource3,
                                                  kOtherOrigin_, kGroup_);
 
-  SetV1EndpointInCache(kGroup1Key1, kReportingSource1, kUrl_);
-  SetV1EndpointInCache(kGroup2Key1, kReportingSource1, kUrl_);
-  SetV1EndpointInCache(kGroup1Key2, kReportingSource2, kUrl_);
-  SetV1EndpointInCache(kGroup2Key2, kReportingSource2, kUrl_);
-  SetV1EndpointInCache(kOtherGroupKey, kReportingSource3, kOtherUrl_);
+  SetV1EndpointInCache(kGroup1Key1, kReportingSource1, kIsolationInfo1, kUrl_);
+  SetV1EndpointInCache(kGroup2Key1, kReportingSource1, kIsolationInfo1, kUrl_);
+  SetV1EndpointInCache(kGroup1Key2, kReportingSource2, kIsolationInfo2, kUrl_);
+  SetV1EndpointInCache(kGroup2Key2, kReportingSource2, kIsolationInfo2, kUrl_);
+  SetV1EndpointInCache(kOtherGroupKey, kReportingSource3, kIsolationInfo3,
+                       kOtherUrl_);
 
   UploadFirstReportAndStartTimer();
 
@@ -826,6 +870,16 @@
   const base::UnguessableToken kReportingSource3 =
       base::UnguessableToken::Create();
 
+  const IsolationInfo kIsolationInfo1 =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther, kOrigin_,
+                            kOrigin_, SiteForCookies::FromOrigin(kOrigin_));
+  const IsolationInfo kIsolationInfo2 =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther, kOrigin_,
+                            kOrigin_, SiteForCookies::FromOrigin(kOrigin_));
+  const IsolationInfo kIsolationInfo3 = IsolationInfo::Create(
+      IsolationInfo::RequestType::kOther, kOtherOrigin_, kOtherOrigin_,
+      SiteForCookies::FromOrigin(kOtherOrigin_));
+
   // Set up identical endpoint configuration for kReportingSource1 and
   // kReportingSource2. kReportingSource3 is independent.
   const ReportingEndpointGroupKey kGroup1Key1(kNik_, kReportingSource1,
@@ -839,11 +893,12 @@
   const ReportingEndpointGroupKey kOtherGroupKey(kOtherNik_, kReportingSource3,
                                                  kOtherOrigin_, kGroup_);
 
-  SetV1EndpointInCache(kGroup1Key1, kReportingSource1, kUrl_);
-  SetV1EndpointInCache(kGroup2Key1, kReportingSource1, kUrl_);
-  SetV1EndpointInCache(kGroup1Key2, kReportingSource2, kUrl_);
-  SetV1EndpointInCache(kGroup2Key2, kReportingSource2, kUrl_);
-  SetV1EndpointInCache(kOtherGroupKey, kReportingSource3, kOtherUrl_);
+  SetV1EndpointInCache(kGroup1Key1, kReportingSource1, kIsolationInfo1, kUrl_);
+  SetV1EndpointInCache(kGroup2Key1, kReportingSource1, kIsolationInfo1, kUrl_);
+  SetV1EndpointInCache(kGroup1Key2, kReportingSource2, kIsolationInfo2, kUrl_);
+  SetV1EndpointInCache(kGroup2Key2, kReportingSource2, kIsolationInfo2, kUrl_);
+  SetV1EndpointInCache(kOtherGroupKey, kReportingSource3, kIsolationInfo3,
+                       kOtherUrl_);
 
   UploadFirstReportAndStartTimer();
 
diff --git a/net/reporting/reporting_endpoint_manager_unittest.cc b/net/reporting/reporting_endpoint_manager_unittest.cc
index 2b5254e..dee77fe 100644
--- a/net/reporting/reporting_endpoint_manager_unittest.cc
+++ b/net/reporting/reporting_endpoint_manager_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/time/time.h"
 #include "base/unguessable_token.h"
 #include "net/base/backoff_entry.h"
+#include "net/base/isolation_info.h"
 #include "net/base/network_isolation_key.h"
 #include "net/base/schemeful_site.h"
 #include "net/reporting/reporting_cache.h"
@@ -140,6 +141,7 @@
   }
   void OnParsedReportingEndpointsHeader(
       const base::UnguessableToken& reporting_source,
+      const IsolationInfo& isolation_info,
       std::vector<ReportingEndpoint> endpoints) override {
     NOTREACHED();
   }
@@ -224,9 +226,15 @@
   }
   void SetV1EndpointForTesting(const ReportingEndpointGroupKey& group_key,
                                const base::UnguessableToken& reporting_source,
+                               const IsolationInfo& isolation_info,
                                const GURL& url) override {
     NOTREACHED();
   }
+  IsolationInfo GetIsolationInfoForEndpoint(
+      const ReportingEndpoint& endpoint) const override {
+    NOTREACHED();
+    return IsolationInfo();
+  }
 
  private:
   const url::Origin expected_origin_;
diff --git a/net/reporting/reporting_garbage_collector_unittest.cc b/net/reporting/reporting_garbage_collector_unittest.cc
index 79854c3a..aa27ee9 100644
--- a/net/reporting/reporting_garbage_collector_unittest.cc
+++ b/net/reporting/reporting_garbage_collector_unittest.cc
@@ -9,6 +9,8 @@
 #include "base/test/simple_test_tick_clock.h"
 #include "base/time/time.h"
 #include "base/timer/mock_timer.h"
+#include "net/base/isolation_info.h"
+#include "net/base/network_isolation_key.h"
 #include "net/reporting/reporting_cache.h"
 #include "net/reporting/reporting_policy.h"
 #include "net/reporting/reporting_report.h"
@@ -30,6 +32,7 @@
   const absl::optional<base::UnguessableToken> kReportingSource_ =
       base::UnguessableToken::Create();
   const NetworkIsolationKey kNik_;
+  const IsolationInfo kIsolationInfo_;
   const GURL kUrl_ = GURL("https://origin/path");
   const std::string kUserAgent_ = "Mozilla/1.0";
   const std::string kGroup_ = "group";
@@ -94,7 +97,8 @@
 TEST_F(ReportingGarbageCollectorTest, ExpiredSource) {
   ReportingEndpointGroupKey group_key(kNik_, kReportingSource_,
                                       url::Origin::Create(kUrl_), kGroup_);
-  cache()->SetV1EndpointForTesting(group_key, *kReportingSource_, kUrl_);
+  cache()->SetV1EndpointForTesting(group_key, *kReportingSource_,
+                                   kIsolationInfo_, kUrl_);
 
   // Mark the source as expired. The source should be removed as soon as
   // garbage collection runs, as there are no queued reports for it.
@@ -113,7 +117,8 @@
 TEST_F(ReportingGarbageCollectorTest, ExpiredSourceWithPendingReports) {
   ReportingEndpointGroupKey group_key(kNik_, kReportingSource_,
                                       url::Origin::Create(kUrl_), kGroup_);
-  cache()->SetV1EndpointForTesting(group_key, *kReportingSource_, kUrl_);
+  cache()->SetV1EndpointForTesting(group_key, *kReportingSource_,
+                                   kIsolationInfo_, kUrl_);
   cache()->AddReport(kReportingSource_, kNik_, kUrl_, kUserAgent_, kGroup_,
                      kType_, std::make_unique<base::DictionaryValue>(), 0,
                      tick_clock()->NowTicks(), 0);
diff --git a/net/reporting/reporting_header_parser.cc b/net/reporting/reporting_header_parser.cc
index 0764197..d80086d 100644
--- a/net/reporting/reporting_header_parser.cc
+++ b/net/reporting/reporting_header_parser.cc
@@ -17,6 +17,7 @@
 #include "base/time/time.h"
 #include "base/values.h"
 #include "net/base/features.h"
+#include "net/base/isolation_info.h"
 #include "net/base/network_isolation_key.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "net/reporting/reporting_cache.h"
@@ -222,14 +223,14 @@
 bool ProcessV1Endpoint(ReportingDelegate* delegate,
                        ReportingCache* cache,
                        const base::UnguessableToken& reporting_source,
-                       const NetworkIsolationKey& network_isolation_key,
+                       const IsolationInfo& isolation_info,
                        const url::Origin& origin,
                        const std::string& endpoint_name,
                        const std::string& endpoint_url_string,
                        ReportingEndpoint& parsed_endpoint_out) {
   DCHECK(!reporting_source.is_empty());
-  ReportingEndpointGroupKey group_key(network_isolation_key, reporting_source,
-                                      origin, endpoint_name);
+  ReportingEndpointGroupKey group_key(isolation_info.network_isolation_key(),
+                                      reporting_source, origin, endpoint_name);
   parsed_endpoint_out.group_key = group_key;
 
   ReportingEndpoint::EndpointInfo parsed_endpoint;
@@ -324,7 +325,7 @@
 void ReportingHeaderParser::ProcessParsedReportingEndpointsHeader(
     ReportingContext* context,
     const base::UnguessableToken& reporting_source,
-    const NetworkIsolationKey& network_isolation_key,
+    const IsolationInfo& isolation_info,
     const url::Origin& origin,
     base::flat_map<std::string, std::string> header) {
   DCHECK(base::FeatureList::IsEnabled(net::features::kDocumentReporting));
@@ -338,9 +339,9 @@
 
   for (const auto& member : header) {
     ReportingEndpoint parsed_endpoint;
-    if (ProcessV1Endpoint(delegate, cache, reporting_source,
-                          network_isolation_key, origin, member.first,
-                          member.second, parsed_endpoint)) {
+    if (ProcessV1Endpoint(delegate, cache, reporting_source, isolation_info,
+                          origin, member.first, member.second,
+                          parsed_endpoint)) {
       parsed_header.push_back(std::move(parsed_endpoint));
     }
   }
@@ -351,7 +352,7 @@
   }
 
   RecordReportingHeaderType(ReportingHeaderType::kReportingEndpoints);
-  cache->OnParsedReportingEndpointsHeader(reporting_source,
+  cache->OnParsedReportingEndpointsHeader(reporting_source, isolation_info,
                                           std::move(parsed_header));
 }
 
diff --git a/net/reporting/reporting_header_parser.h b/net/reporting/reporting_header_parser.h
index 71a7101..5db263e 100644
--- a/net/reporting/reporting_header_parser.h
+++ b/net/reporting/reporting_header_parser.h
@@ -21,6 +21,7 @@
 
 namespace net {
 
+class IsolationInfo;
 class NetworkIsolationKey;
 class ReportingContext;
 
@@ -58,7 +59,7 @@
   static void ProcessParsedReportingEndpointsHeader(
       ReportingContext* context,
       const base::UnguessableToken& reporting_source,
-      const NetworkIsolationKey& network_isolation_key,
+      const IsolationInfo& isolation_info,
       const url::Origin& origin,
       base::flat_map<std::string, std::string> parsed_header);
 
diff --git a/net/reporting/reporting_header_parser_unittest.cc b/net/reporting/reporting_header_parser_unittest.cc
index 2be6544..ec2023d 100644
--- a/net/reporting/reporting_header_parser_unittest.cc
+++ b/net/reporting/reporting_header_parser_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/time/time.h"
 #include "base/values.h"
 #include "net/base/features.h"
+#include "net/base/isolation_info.h"
 #include "net/base/schemeful_site.h"
 #include "net/reporting/mock_persistent_reporting_store.h"
 #include "net/reporting/reporting_cache.h"
@@ -74,6 +75,11 @@
       NetworkIsolationKey(SchemefulSite(kOrigin1_), SchemefulSite(kOrigin1_));
   const NetworkIsolationKey kOtherNik_ =
       NetworkIsolationKey(SchemefulSite(kOrigin2_), SchemefulSite(kOrigin2_));
+  const IsolationInfo kIsolationInfo_ =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther,
+                            kOrigin1_,
+                            kOrigin1_,
+                            SiteForCookies::FromOrigin(kOrigin1_));
   const GURL kUrlEtld_ = GURL("https://co.uk/foo.html/");
   const url::Origin kOriginEtld_ = url::Origin::Create(kUrlEtld_);
   const GURL kEndpoint1_ = GURL("https://endpoint1.test/");
@@ -1771,7 +1777,7 @@
   }
 
   void ParseHeader(const base::UnguessableToken& reporting_source,
-                   const NetworkIsolationKey& network_isolation_key,
+                   const IsolationInfo& isolation_info,
                    const url::Origin& origin,
                    const std::string& header_string) {
     absl::optional<base::flat_map<std::string, std::string>> header_map =
@@ -1779,19 +1785,17 @@
 
     if (header_map) {
       ReportingHeaderParser::ProcessParsedReportingEndpointsHeader(
-          context(), reporting_source, network_isolation_key, origin,
-          *header_map);
+          context(), reporting_source, isolation_info, origin, *header_map);
     }
   }
   void ProcessParsedHeader(
       const base::UnguessableToken& reporting_source,
-      const NetworkIsolationKey& network_isolation_key,
+      const IsolationInfo& isolation_info,
       const url::Origin& origin,
       const absl::optional<base::flat_map<std::string, std::string>>&
           header_map) {
     ReportingHeaderParser::ProcessParsedReportingEndpointsHeader(
-        context(), reporting_source, network_isolation_key, origin,
-        *header_map);
+        context(), reporting_source, isolation_info, origin, *header_map);
   }
 
   const base::UnguessableToken kReportingSource_ =
@@ -1835,7 +1839,8 @@
         << "Syntactically valid Reporting-Endpoints header (\""
         << test_case.description << ": \"" << test_case.header_value
         << "\") parsed as invalid.";
-    ProcessParsedHeader(kReportingSource_, kNik_, kOrigin1_, parsed_result);
+    ProcessParsedHeader(kReportingSource_, kIsolationInfo_, kOrigin1_,
+                        parsed_result);
 
     invalid_case_count++;
     histograms.ExpectBucketCount(
@@ -1874,7 +1879,8 @@
   std::string header =
       ConstructHeaderGroupString(MakeEndpointGroup(kGroup1_, endpoints));
   auto parsed_result = ParseReportingEndpoints(header);
-  ProcessParsedHeader(kReportingSource_, kNik_, kOrigin1_, parsed_result);
+  ProcessParsedHeader(kReportingSource_, kIsolationInfo_, kOrigin1_,
+                      parsed_result);
 
   // Ensure that the endpoint was not inserted into the persistent endpoint
   // groups used for v0 reporting.
@@ -1884,6 +1890,9 @@
       cache()->GetV1EndpointForTesting(kReportingSource_, kGroup1_);
   EXPECT_TRUE(endpoint);
 
+  IsolationInfo isolation_info = cache()->GetIsolationInfoForEndpoint(endpoint);
+  EXPECT_TRUE(isolation_info.IsEqualForTesting(kIsolationInfo_));
+
   EXPECT_EQ(kOrigin1_, endpoint.group_key.origin);
   EXPECT_EQ(kGroup1_, endpoint.group_key.group_name);
   EXPECT_EQ(kEndpoint1_, endpoint.info.url);
@@ -1907,7 +1916,8 @@
   base::HistogramTester histograms;
   std::string header = "group1=\"/path-absolute-url\"";
   auto parsed_result = ParseReportingEndpoints(header);
-  ProcessParsedHeader(kReportingSource_, kNik_, kOrigin1_, parsed_result);
+  ProcessParsedHeader(kReportingSource_, kIsolationInfo_, kOrigin1_,
+                      parsed_result);
 
   // Ensure that the endpoint was not inserted into the persistent endpoint
   // groups used for v0 reporting.
diff --git a/net/reporting/reporting_service.cc b/net/reporting/reporting_service.cc
index 58dea43..a6388e22 100644
--- a/net/reporting/reporting_service.cc
+++ b/net/reporting/reporting_service.cc
@@ -16,6 +16,7 @@
 #include "base/time/time.h"
 #include "base/values.h"
 #include "net/base/features.h"
+#include "net/base/isolation_info.h"
 #include "net/http/structured_headers.h"
 #include "net/reporting/reporting_browsing_data_remover.h"
 #include "net/reporting/reporting_cache.h"
@@ -62,14 +63,13 @@
   void SetDocumentReportingEndpoints(
       const base::UnguessableToken& reporting_source,
       const url::Origin& origin,
-      const net::NetworkIsolationKey& network_isolation_key,
+      const IsolationInfo& isolation_info,
       const base::flat_map<std::string, std::string>& endpoints) override {
     DCHECK(!reporting_source.is_empty());
     DoOrBacklogTask(
         base::BindOnce(&ReportingServiceImpl::DoSetDocumentReportingEndpoints,
-                       base::Unretained(this), reporting_source,
-                       FixupNetworkIsolationKey(network_isolation_key), origin,
-                       std::move(endpoints)));
+                       base::Unretained(this), reporting_source, isolation_info,
+                       origin, std::move(endpoints)));
   }
 
   void SendReportsAndRemoveSource(
@@ -221,12 +221,12 @@
 
   void DoSetDocumentReportingEndpoints(
       const base::UnguessableToken& reporting_source,
-      const NetworkIsolationKey& network_isolation_key,
+      const IsolationInfo& isolation_info,
       const url::Origin& origin,
       base::flat_map<std::string, std::string> header_value) {
     DCHECK(initialized_);
     ReportingHeaderParser::ProcessParsedReportingEndpointsHeader(
-        context_.get(), reporting_source, network_isolation_key, origin,
+        context_.get(), reporting_source, isolation_info, origin,
         std::move(header_value));
   }
 
diff --git a/net/reporting/reporting_service.h b/net/reporting/reporting_service.h
index 8643e16..68ae61d 100644
--- a/net/reporting/reporting_service.h
+++ b/net/reporting/reporting_service.h
@@ -25,6 +25,7 @@
 
 namespace net {
 
+class IsolationInfo;
 class NetworkIsolationKey;
 class ReportingContext;
 struct ReportingPolicy;
@@ -90,15 +91,11 @@
   // this header was received, and must not be empty.
   // |endpoints| is a mapping of endpoint names to URLs.
   // |origin| is the origin of the reporting source, and
-  // |network_isolation_key| is the appropriate NIK for that source.
-  // (The isolation provided by Reporting-Endpoints is stronger than that
-  // provided by Report-To, so the origin and NIK aren't strictly necessary,
-  // but are currently required here because of shared infrastructure between
-  // the two versions of the reporting API.)
+  // |isolation_info| is the appropriate IsolationInfo struct for that source.
   virtual void SetDocumentReportingEndpoints(
       const base::UnguessableToken& reporting_source,
       const url::Origin& origin,
-      const NetworkIsolationKey& network_isolation_key,
+      const IsolationInfo& isolation_info,
       const base::flat_map<std::string, std::string>& endpoints) = 0;
 
   // Attempts to send any queued reports and removes all associated
diff --git a/net/reporting/reporting_service_unittest.cc b/net/reporting/reporting_service_unittest.cc
index 510e498..ab563cc 100644
--- a/net/reporting/reporting_service_unittest.cc
+++ b/net/reporting/reporting_service_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/time/tick_clock.h"
 #include "base/values.h"
 #include "net/base/features.h"
+#include "net/base/isolation_info.h"
 #include "net/base/network_isolation_key.h"
 #include "net/base/schemeful_site.h"
 #include "net/reporting/mock_persistent_reporting_store.h"
@@ -61,6 +62,11 @@
       ReportingEndpointGroupKey(kNik_, kOrigin_, kGroup_);
   const ReportingEndpointGroupKey kGroupKey2_ =
       ReportingEndpointGroupKey(kNik2_, kOrigin2_, kGroup_);
+  const IsolationInfo kIsolationInfo_ =
+      IsolationInfo::Create(IsolationInfo::RequestType::kOther,
+                            kOrigin_,
+                            kOrigin_,
+                            SiteForCookies::FromOrigin(kOrigin_));
 
   ReportingServiceTest() {
     feature_list_.InitAndEnableFeature(
@@ -196,8 +202,8 @@
   auto parsed_header =
       ParseReportingEndpoints(kGroup_ + "=\"" + kEndpoint_.spec() + "\"");
   ASSERT_TRUE(parsed_header.has_value());
-  service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_, kNik_,
-                                           *parsed_header);
+  service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_,
+                                           kIsolationInfo_, *parsed_header);
   FinishLoading(true /* load_success */);
 
   // Endpoint should not be part of the persistent store.
@@ -214,8 +220,8 @@
       ParseReportingEndpoints(kGroup_ + "=\"" + kEndpoint_.spec() + "\", " +
                               kGroup2_ + "=\"" + kEndpoint2_.spec() + "\"");
   ASSERT_TRUE(parsed_header.has_value());
-  service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_, kNik_,
-                                           *parsed_header);
+  service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_,
+                                           kIsolationInfo_, *parsed_header);
   // This report should be sent immediately, starting the delivery agent timer.
   service()->QueueReport(kUrl_, kReportingSource_, kNik_, kUserAgent_, kGroup_,
                          kType_, std::make_unique<base::DictionaryValue>(), 0);
@@ -249,8 +255,8 @@
       ParseReportingEndpoints(kGroup_ + "=\"" + kEndpoint_.spec() + "\", " +
                               kGroup2_ + "=\"" + kEndpoint2_.spec() + "\"");
   ASSERT_TRUE(parsed_header.has_value());
-  service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_, kNik_,
-                                           *parsed_header);
+  service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_,
+                                           kIsolationInfo_, *parsed_header);
   // This report should be sent immediately, starting the delivery agent timer.
   service()->QueueReport(kUrl_, kReportingSource_, kNik_, kUserAgent_, kGroup_,
                          kType_, std::make_unique<base::DictionaryValue>(), 0);
@@ -291,8 +297,8 @@
   feature_list.InitAndEnableFeature(net::features::kDocumentReporting);
   auto parsed_header = ParseReportingEndpoints(kGroup_ + "=\"/path-absolute\"");
   ASSERT_TRUE(parsed_header.has_value());
-  service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_, kNik_,
-                                           *parsed_header);
+  service()->SetDocumentReportingEndpoints(*kReportingSource_, kOrigin_,
+                                           kIsolationInfo_, *parsed_header);
   FinishLoading(true /* load_success */);
 
   // Endpoint should not be part of the persistent store.
diff --git a/net/reporting/reporting_test_util.cc b/net/reporting/reporting_test_util.cc
index d3a637a..9f56dfa 100644
--- a/net/reporting/reporting_test_util.cc
+++ b/net/reporting/reporting_test_util.cc
@@ -17,6 +17,7 @@
 #include "base/test/simple_test_clock.h"
 #include "base/test/simple_test_tick_clock.h"
 #include "base/timer/mock_timer.h"
+#include "net/base/isolation_info.h"
 #include "net/base/network_isolation_key.h"
 #include "net/reporting/reporting_cache.h"
 #include "net/reporting/reporting_context.h"
@@ -38,13 +39,13 @@
  public:
   PendingUploadImpl(const url::Origin& report_origin,
                     const GURL& url,
-                    const NetworkIsolationKey& network_isolation_key,
+                    const IsolationInfo& isolation_info,
                     const std::string& json,
                     ReportingUploader::UploadCallback callback,
                     base::OnceCallback<void(PendingUpload*)> complete_callback)
       : report_origin_(report_origin),
         url_(url),
-        network_isolation_key_(network_isolation_key),
+        isolation_info_(isolation_info),
         json_(json),
         callback_(std::move(callback)),
         complete_callback_(std::move(complete_callback)) {}
@@ -68,7 +69,7 @@
  private:
   url::Origin report_origin_;
   GURL url_;
-  NetworkIsolationKey network_isolation_key_;
+  IsolationInfo isolation_info_;
   std::string json_;
   ReportingUploader::UploadCallback callback_;
   base::OnceCallback<void(PendingUpload*)> complete_callback_;
@@ -103,15 +104,15 @@
 TestReportingUploader::TestReportingUploader() = default;
 TestReportingUploader::~TestReportingUploader() = default;
 
-void TestReportingUploader::StartUpload(
-    const url::Origin& report_origin,
-    const GURL& url,
-    const NetworkIsolationKey& network_isolation_key,
-    const std::string& json,
-    int max_depth,
-    UploadCallback callback) {
+void TestReportingUploader::StartUpload(const url::Origin& report_origin,
+                                        const GURL& url,
+                                        const IsolationInfo& isolation_info,
+                                        const std::string& json,
+                                        int max_depth,
+                                        bool eligible_for_credentials,
+                                        UploadCallback callback) {
   pending_uploads_.push_back(std::make_unique<PendingUploadImpl>(
-      report_origin, url, network_isolation_key, json, std::move(callback),
+      report_origin, url, isolation_info, json, std::move(callback),
       base::BindOnce(&ErasePendingUpload, &pending_uploads_)));
 }
 
@@ -231,8 +232,10 @@
 void ReportingTestBase::SetV1EndpointInCache(
     const ReportingEndpointGroupKey& group_key,
     const base::UnguessableToken& reporting_source,
+    const IsolationInfo& isolation_info,
     const GURL& url) {
-  cache()->SetV1EndpointForTesting(group_key, reporting_source, url);
+  cache()->SetV1EndpointForTesting(group_key, reporting_source, isolation_info,
+                                   url);
 }
 
 bool ReportingTestBase::EndpointExistsInCache(
diff --git a/net/reporting/reporting_test_util.h b/net/reporting/reporting_test_util.h
index 2659997..fbf70e0 100644
--- a/net/reporting/reporting_test_util.h
+++ b/net/reporting/reporting_test_util.h
@@ -40,7 +40,7 @@
 
 namespace net {
 
-class NetworkIsolationKey;
+class IsolationInfo;
 struct ReportingEndpoint;
 class ReportingGarbageCollector;
 
@@ -89,9 +89,10 @@
 
   void StartUpload(const url::Origin& report_origin,
                    const GURL& url,
-                   const NetworkIsolationKey& network_isolation_key,
+                   const IsolationInfo& isolation_info,
                    const std::string& json,
                    int max_depth,
+                   bool eligible_for_credentials,
                    UploadCallback callback) override;
 
   void OnShutdown() override;
@@ -218,6 +219,7 @@
   // endpoints map using |reporting_source| as key.
   void SetV1EndpointInCache(const ReportingEndpointGroupKey& group_key,
                             const base::UnguessableToken& reporting_source,
+                            const IsolationInfo& isolation_info,
                             const GURL& url);
 
   // Returns whether an endpoint with the given properties exists in the cache.
@@ -333,7 +335,7 @@
   void SetDocumentReportingEndpoints(
       const base::UnguessableToken& reporting_source,
       const url::Origin& origin,
-      const net::NetworkIsolationKey& network_isolation_key,
+      const IsolationInfo& isolation_info,
       const base::flat_map<std::string, std::string>& endpoints) override {}
 
   void SendReportsAndRemoveSource(
diff --git a/net/reporting/reporting_uploader.cc b/net/reporting/reporting_uploader.cc
index 49c7eac..ed43507 100644
--- a/net/reporting/reporting_uploader.cc
+++ b/net/reporting/reporting_uploader.cc
@@ -81,14 +81,14 @@
 
   PendingUpload(const url::Origin& report_origin,
                 const GURL& url,
-                const NetworkIsolationKey& network_isolation_key,
+                const IsolationInfo& isolation_info,
                 const std::string& json,
                 int max_depth,
                 ReportingUploader::UploadCallback callback)
       : state(CREATED),
         report_origin(report_origin),
         url(url),
-        network_isolation_key(network_isolation_key),
+        isolation_info(isolation_info),
         payload_reader(UploadOwnedBytesElementReader::CreateWithString(json)),
         max_depth(max_depth),
         callback(std::move(callback)) {}
@@ -100,7 +100,7 @@
   State state;
   const url::Origin report_origin;
   const GURL url;
-  const NetworkIsolationKey network_isolation_key;
+  const IsolationInfo isolation_info;
   std::unique_ptr<UploadElementReader> payload_reader;
   int max_depth;
   ReportingUploader::UploadCallback callback;
@@ -122,18 +122,19 @@
 
   void StartUpload(const url::Origin& report_origin,
                    const GURL& url,
-                   const NetworkIsolationKey& network_isolation_key,
+                   const IsolationInfo& isolation_info,
                    const std::string& json,
                    int max_depth,
+                   bool eligible_for_credentials,
                    UploadCallback callback) override {
-    auto upload = std::make_unique<PendingUpload>(
-        report_origin, url, network_isolation_key, json, max_depth,
-        std::move(callback));
+    auto upload =
+        std::make_unique<PendingUpload>(report_origin, url, isolation_info,
+                                        json, max_depth, std::move(callback));
     auto collector_origin = url::Origin::Create(url);
     if (collector_origin == report_origin) {
       // Skip the preflight check if the reports are being sent to the same
       // origin as the requests they describe.
-      StartPayloadRequest(std::move(upload));
+      StartPayloadRequest(std::move(upload), eligible_for_credentials);
     } else {
       StartPreflightRequest(std::move(upload));
     }
@@ -155,8 +156,7 @@
 
     upload->request->SetLoadFlags(LOAD_DISABLE_CACHE);
     upload->request->set_allow_credentials(false);
-    upload->request->set_isolation_info(IsolationInfo::CreatePartial(
-        IsolationInfo::RequestType::kOther, upload->network_isolation_key));
+    upload->request->set_isolation_info(upload->isolation_info);
 
     upload->request->SetExtraRequestHeaderByName(
         HttpRequestHeaders::kOrigin, upload->report_origin.Serialize(), true);
@@ -176,7 +176,8 @@
     raw_request->Start();
   }
 
-  void StartPayloadRequest(std::unique_ptr<PendingUpload> upload) {
+  void StartPayloadRequest(std::unique_ptr<PendingUpload> upload,
+                           bool eligible_for_credentials) {
     DCHECK(upload->state == PendingUpload::CREATED ||
            upload->state == PendingUpload::SENDING_PREFLIGHT);
 
@@ -186,9 +187,19 @@
     upload->request->set_method("POST");
 
     upload->request->SetLoadFlags(LOAD_DISABLE_CACHE);
-    upload->request->set_allow_credentials(false);
-    upload->request->set_isolation_info(IsolationInfo::CreatePartial(
-        IsolationInfo::RequestType::kOther, upload->network_isolation_key));
+
+    // Credentials are sent for V1 reports, if the endpoint is same-origin with
+    // the site generating the report (this will be set to false either by the
+    // delivery agent determining that this is a V0 report, or by `StartUpload`
+    // determining that this is a cross-origin case, and taking the CORS
+    // preflight path).
+    upload->request->set_allow_credentials(eligible_for_credentials);
+    // The site for cookies is taken from the reporting source's IsolationInfo,
+    // in the case of V1 reporting endpoints, and will be null for V0 reports.
+    upload->request->set_site_for_cookies(
+        upload->isolation_info.site_for_cookies());
+    upload->request->set_initiator(upload->isolation_info.frame_origin());
+    upload->request->set_isolation_info(upload->isolation_info);
 
     upload->request->SetExtraRequestHeaderByName(
         HttpRequestHeaders::kContentType, kUploadContentType, true);
@@ -286,8 +297,9 @@
       upload->RunCallback(ReportingUploader::Outcome::FAILURE);
       return;
     }
-
-    StartPayloadRequest(std::move(upload));
+    // Any upload which required CORS should not receive credentials, as they
+    // are sent to same-origin endpoints only.
+    StartPayloadRequest(std::move(upload), /*eligible_for_credentials=*/false);
   }
 
   void HandlePayloadResponse(std::unique_ptr<PendingUpload> upload,
diff --git a/net/reporting/reporting_uploader.h b/net/reporting/reporting_uploader.h
index 9fa0318..7749cd54e 100644
--- a/net/reporting/reporting_uploader.h
+++ b/net/reporting/reporting_uploader.h
@@ -19,7 +19,7 @@
 
 namespace net {
 
-class NetworkIsolationKey;
+class IsolationInfo;
 class URLRequestContext;
 
 // Uploads already-serialized reports and converts responses to one of the
@@ -35,12 +35,14 @@
   // Starts to upload the reports in |json| (properly tagged as JSON data) to
   // |url|, and calls |callback| when complete (whether successful or not).
   // All of the reports in |json| must describe requests to the same origin;
-  // |report_origin| must be that origin.
+  // |report_origin| must be that origin. Credentials may be sent with the
+  // upload if |eligible_for_credentials| is true.
   virtual void StartUpload(const url::Origin& report_origin,
                            const GURL& url,
-                           const NetworkIsolationKey& network_isolation_key,
+                           const IsolationInfo& isolation_info,
                            const std::string& json,
                            int max_depth,
+                           bool eligible_for_credentials,
                            UploadCallback callback) = 0;
 
   // Cancels pending uploads.
diff --git a/net/reporting/reporting_uploader_unittest.cc b/net/reporting/reporting_uploader_unittest.cc
index 3f677cc..53bd470f 100644
--- a/net/reporting/reporting_uploader_unittest.cc
+++ b/net/reporting/reporting_uploader_unittest.cc
@@ -140,8 +140,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 }
 
@@ -151,8 +152,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_EQ(ReportingUploader::Outcome::SUCCESS, callback.outcome());
@@ -164,8 +166,8 @@
   ASSERT_TRUE(server_.ShutdownAndWaitUntilComplete());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, url, NetworkIsolationKey(), kUploadBody, 0,
-                         callback.callback());
+  uploader_->StartUpload(kOrigin, url, IsolationInfo::CreateTransient(),
+                         kUploadBody, 0, false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
@@ -177,8 +179,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
@@ -191,8 +194,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
@@ -216,8 +220,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_TRUE(preflight_received);
@@ -234,8 +239,8 @@
   TestUploadCallback callback;
   auto server_origin = url::Origin::Create(server_.base_url());
   uploader_->StartUpload(server_origin, server_.GetURL("/"),
-                         NetworkIsolationKey(), kUploadBody, 0,
-                         callback.callback());
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_FALSE(preflight_received);
@@ -260,8 +265,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
@@ -290,8 +296,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
@@ -320,8 +327,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
@@ -350,8 +358,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_EQ(ReportingUploader::Outcome::FAILURE, callback.outcome());
@@ -364,8 +373,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_EQ(ReportingUploader::Outcome::REMOVE_ENDPOINT, callback.outcome());
@@ -408,8 +418,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_TRUE(followed);
@@ -431,8 +442,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, callback.callback());
   callback.WaitForCall();
 
   EXPECT_FALSE(followed);
@@ -462,8 +474,9 @@
   ASSERT_TRUE(cookie_callback.result().status.IsInclude());
 
   TestUploadCallback upload_callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, upload_callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, upload_callback.callback());
   upload_callback.WaitForCall();
 }
 
@@ -483,8 +496,9 @@
   ASSERT_TRUE(server_.Start());
 
   TestUploadCallback upload_callback;
-  uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                         kUploadBody, 0, upload_callback.callback());
+  uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                         IsolationInfo::CreateTransient(), kUploadBody, 0,
+                         false, upload_callback.callback());
   upload_callback.WaitForCall();
 
   GetCookieListCallback cookie_callback;
@@ -523,16 +537,18 @@
 
   {
     TestUploadCallback callback;
-    uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                           kUploadBody, 0, callback.callback());
+    uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                           IsolationInfo::CreateTransient(), kUploadBody, 0,
+                           false, callback.callback());
     callback.WaitForCall();
   }
   EXPECT_EQ(1, request_count);
 
   {
     TestUploadCallback callback;
-    uploader_->StartUpload(kOrigin, server_.GetURL("/"), NetworkIsolationKey(),
-                           kUploadBody, 0, callback.callback());
+    uploader_->StartUpload(kOrigin, server_.GetURL("/"),
+                           IsolationInfo::CreateTransient(), kUploadBody, 0,
+                           false, callback.callback());
     callback.WaitForCall();
   }
   EXPECT_EQ(2, request_count);
@@ -556,6 +572,10 @@
   ASSERT_NE(kSite1, kSite2);
   const NetworkIsolationKey kNetworkIsolationKey1(kSite1, kSite1);
   const NetworkIsolationKey kNetworkIsolationKey2(kSite2, kSite2);
+  const IsolationInfo kIsolationInfo1 = IsolationInfo::CreatePartial(
+      IsolationInfo::RequestType::kOther, kNetworkIsolationKey1);
+  const IsolationInfo kIsolationInfo2 = IsolationInfo::CreatePartial(
+      IsolationInfo::RequestType::kOther, kNetworkIsolationKey2);
 
   MockClientSocketFactory socket_factory;
   TestURLRequestContext context(true /* delay_initialization */);
@@ -627,9 +647,8 @@
   TestUploadCallback callback1;
   std::unique_ptr<ReportingUploader> uploader1 =
       ReportingUploader::Create(&context);
-  uploader1->StartUpload(kOrigin, GURL("https://origin/1"),
-                         kNetworkIsolationKey1, kUploadBody, 0,
-                         callback1.callback());
+  uploader1->StartUpload(kOrigin, GURL("https://origin/1"), kIsolationInfo1,
+                         kUploadBody, 0, false, callback1.callback());
   callback1.WaitForCall();
   EXPECT_EQ(ReportingUploader::Outcome::SUCCESS, callback1.outcome());
 
@@ -641,15 +660,13 @@
   TestUploadCallback callback2;
   std::unique_ptr<ReportingUploader> uploader2 =
       ReportingUploader::Create(&context);
-  uploader2->StartUpload(kOrigin, GURL("https://origin/2"),
-                         kNetworkIsolationKey2, kUploadBody, 0,
-                         callback2.callback());
+  uploader2->StartUpload(kOrigin, GURL("https://origin/2"), kIsolationInfo2,
+                         kUploadBody, 0, false, callback2.callback());
   TestUploadCallback callback3;
   std::unique_ptr<ReportingUploader> uploader3 =
       ReportingUploader::Create(&context);
-  uploader3->StartUpload(kOrigin, GURL("https://origin/3"),
-                         kNetworkIsolationKey1, kUploadBody, 0,
-                         callback3.callback());
+  uploader3->StartUpload(kOrigin, GURL("https://origin/3"), kIsolationInfo1,
+                         kUploadBody, 0, false, callback3.callback());
 
   callback2.WaitForCall();
   EXPECT_EQ(ReportingUploader::Outcome::SUCCESS, callback2.outcome());
diff --git a/net/url_request/url_request_context_builder_unittest.cc b/net/url_request/url_request_context_builder_unittest.cc
index a4a694b..90fbdaae 100644
--- a/net/url_request/url_request_context_builder_unittest.cc
+++ b/net/url_request/url_request_context_builder_unittest.cc
@@ -206,8 +206,9 @@
   // Queue a pending upload.
   GURL url("https://www.foo.test");
   context->reporting_service()->GetContextForTesting()->uploader()->StartUpload(
-      url::Origin::Create(url), url, NetworkIsolationKey(), "report body", 0,
-      base::DoNothing());
+      url::Origin::Create(url), url, IsolationInfo::CreateTransient(),
+      "report body", 0,
+      /*eligible_for_credentials=*/false, base::DoNothing());
   base::RunLoop().RunUntilIdle();
   ASSERT_EQ(1, context->reporting_service()
                    ->GetContextForTesting()
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 2dda0e7..f8fddf1 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -43,6 +43,7 @@
 #include "crypto/sha2.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "net/base/features.h"
+#include "net/base/isolation_info.h"
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/base/network_delegate.h"
@@ -954,14 +955,16 @@
 void NetworkContext::SetDocumentReportingEndpoints(
     const base::UnguessableToken& reporting_source,
     const url::Origin& origin,
-    const net::NetworkIsolationKey& network_isolation_key,
+    const net::IsolationInfo& isolation_info,
     const base::flat_map<std::string, std::string>& endpoints) {
   DCHECK(!reporting_source.is_empty());
+  DCHECK_EQ(net::IsolationInfo::RequestType::kOther,
+            isolation_info.request_type());
   net::ReportingService* reporting_service =
       url_request_context()->reporting_service();
   if (reporting_service) {
-    reporting_service->SetDocumentReportingEndpoints(
-        reporting_source, origin, network_isolation_key, endpoints);
+    reporting_service->SetDocumentReportingEndpoints(reporting_source, origin,
+                                                     isolation_info, endpoints);
   }
 }
 
@@ -1111,7 +1114,7 @@
 void NetworkContext::SetDocumentReportingEndpoints(
     const base::UnguessableToken& reporting_source,
     const url::Origin& origin,
-    const net::NetworkIsolationKey& network_isolation_key,
+    const net::IsolationInfo& isolation_info,
     const base::flat_map<std::string, std::string>& endpoints) {
   NOTREACHED();
 }
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 45974ba..9f20450 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -414,7 +414,7 @@
   void SetDocumentReportingEndpoints(
       const base::UnguessableToken& reporting_source,
       const url::Origin& origin,
-      const net::NetworkIsolationKey& network_isolation_key,
+      const net::IsolationInfo& isolation_info,
       const base::flat_map<std::string, std::string>& endpoints) override;
   void SendReportsAndRemoveSource(
       const base::UnguessableToken& reporting_source) override;
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 6d2ab19..f3f625c 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -941,18 +941,19 @@
   // |reporting_source| is a token which identifies the document or worker with
   // which the header was received. The endpoints configured by this method are
   // only used to deliver reports which are queued by that same source.
-  // |network_isolation_key| is used when creating the endpoints in the
-  // ReportingCache, and will need to match the key sent when reports are queued
-  // in order for these reporting endpoints to be considered for delivery,
-  // although this should always be the case when reports are queued by the
-  // same source which configured the endpoints.
+  // |isolation_info| is used when creating the endpoints in the ReportingCache
+  // and when determining which credentials to send with uploaded reports. Its
+  // network isolation key will need to match the key sent when reports are
+  // queued, in order for these reporting endpoints to be considered for
+  // delivery, although this should always be the case when reports are queued
+  // by the same source which configured the endpoints.
   // |endpoints| is a mapping of endpoint name to URL (URLs here are represented
   // as strings, and will be rejected if they fail to parse or are not secure).
   //
   // Spec: https://w3c.github.io/reporting/#header
   SetDocumentReportingEndpoints(
       mojo_base.mojom.UnguessableToken reporting_source,
-      url.mojom.Origin origin, NetworkIsolationKey network_isolation_key,
+      url.mojom.Origin origin, IsolationInfo isolation_info,
       map<string,string> endpoints);
 
   // Queues any outstanding reports for a single |reporting_source| (which
diff --git a/services/network/test/test_network_context.h b/services/network/test/test_network_context.h
index f1fbbf1..e41a44de 100644
--- a/services/network/test/test_network_context.h
+++ b/services/network/test/test_network_context.h
@@ -115,7 +115,7 @@
   void SetDocumentReportingEndpoints(
       const base::UnguessableToken& reporting_source,
       const url::Origin& origin,
-      const net::NetworkIsolationKey& network_isolation_key,
+      const net::IsolationInfo& isolation_info,
       const base::flat_map<std::string, std::string>& endpoints) override {}
   void SendReportsAndRemoveSource(
       const base::UnguessableToken& reporting_source) override {}
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 9f37aa04..634c5d3 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6856,9 +6856,6 @@
 crbug.com/1241860 http/tests/inspector-protocol/issues/cors-issues.js [ Skip ]
 crbug.com/1241860 http/tests/inspector-protocol/issues/renderer-cors-issues.js [ Skip ]
 
-# Credentials in same-origin Reporting-API reports are currently not implemented.
-crbug.com/1163645 external/wpt/reporting/same-origin-report-credentials.https.sub.html [ Failure ]
-
 # [CompositeClipPathAnimations] failing test
 crbug.com/1249071 virtual/composite-clip-path-animation/external/wpt/css/css-masking/clip-path/animations/clip-path-animation-filter.html [ Crash Failure Pass ]
 
diff --git a/third_party/blink/web_tests/external/wpt/cookies/resources/setSameSiteDomain.py b/third_party/blink/web_tests/external/wpt/cookies/resources/setSameSiteDomain.py
new file mode 100644
index 0000000..c8b7a71
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/cookies/resources/setSameSiteDomain.py
@@ -0,0 +1,36 @@
+from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+    """Respond to `/cookie/set/samesite?{value}` by setting four cookies:
+    1. `samesite_strict={value};SameSite=Strict;path=/;domain={host}`
+    2. `samesite_lax={value};SameSite=Lax;path=/;domain={host}`
+    3. `samesite_none={value};SameSite=None;path=/;Secure;domain={host}`
+    4. `samesite_unspecified={value};path=/;domain={host}`
+    Where {host} is the hostname from which this page is served. (Requesting this resource
+    without a Host header will result in a 500 server error.)
+    Then navigate to a page that will post a message back to the opener with the set cookies"""
+    headers = setNoCacheAndCORSHeaders(request, response)
+    value = isomorphic_encode(request.url_parts.query)
+    host_header = request.headers['host']
+    hostname = host_header.split(b":")[0]
+    host = isomorphic_encode(hostname)
+    headers.append((b"Content-Type", b"text/html; charset=utf-8"))
+    headers.append(makeCookieHeader(b"samesite_strict", value, {b"SameSite":b"Strict", b"path":b"/", b"domain":host}))
+    headers.append(makeCookieHeader(b"samesite_lax", value, {b"SameSite":b"Lax", b"path":b"/", b"domain":host}))
+    # SameSite=None cookies must be Secure.
+    headers.append(makeCookieHeader(b"samesite_none", value, {b"SameSite":b"None", b"path":b"/", b"Secure": b"", b"domain":host}))
+    headers.append(makeCookieHeader(b"samesite_unspecified", value, {b"path":b"/", b"domain":host}))
+
+    document = b"""
+<!DOCTYPE html>
+<script>
+  // A same-site navigation, which should attach all cookies including SameSite ones.
+  // This is necessary because this page may have been reached via a cross-site navigation, so
+  // we might not have access to some SameSite cookies from here.
+  window.location = "../samesite/resources/echo-cookies.html";
+</script>
+"""
+
+    return headers, document
diff --git a/third_party/blink/web_tests/external/wpt/reporting/cross-origin-same-site-credentials.https.sub.html b/third_party/blink/web_tests/external/wpt/reporting/cross-origin-same-site-credentials.https.sub.html
new file mode 100644
index 0000000..2f3f5fe
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/reporting/cross-origin-same-site-credentials.https.sub.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that credentials are sent properly in a cross-origin but same-site nested context</title>
+  <script src='/https/chromium.googlesource.com/resources/testharness.js'></script>
+  <script src='/https/chromium.googlesource.com/resources/testharnessreport.js'></script>
+  <script src='resources/report-helper.js'></script>
+</head>
+<body>
+  <script>
+    const base_url = `${location.protocol}//${location.host}`;
+    const endpoint = `${base_url}/reporting/resources/report.py`;
+    const id = 'd0d517bf-891b-457a-b970-8b2b2c81a0bf';
+
+    promise_test(async t => {
+      // If this is not run from the expected origin, then the A->A->www.A frame embedding will not be correct,
+      // and the cookies set in the top-level page will never be returned with the reports.
+      assert_true(location.href.startsWith("https://{{hosts[][]}}:{{ports[https][0]}}/"),
+                  "Test running on unexpected origin; subsequent assertions will fail.");
+
+      // Set credentials, and set up test to clear them afterwards. Cookies are set with the Domain
+      // attribute, so that they may be sent to same-site resources.
+      await fetch('/cookies/resources/setSameSiteDomain.py?reporting', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+      t.add_cleanup(() => fetch("/cookies/resources/dropSameSite.py", {mode: 'no-cors', credentials: 'include', cache: 'no-store'}));
+
+      // Insert a same-origin frame, which will then frame a same-site but cross-origin page to
+      // trigger a CSP error.
+      const frame = document.createElement('iframe');
+      frame.src = "https://{{hosts[][]}}:{{ports[https][0]}}/reporting/resources/middle-frame.https.sub.html?host={{hosts[][www]}}";
+
+      // Wait for the inner frame to signal that the report has been generated.
+      await new Promise(resolve => {
+        window.addEventListener('message', ev => {
+          if (ev.data === "done")
+            resolve(ev.data);
+          });
+        document.body.appendChild(frame);
+      });
+
+      const reports = await pollReports(endpoint, id);
+      checkReportExists(reports, 'csp-violation', "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/same-origin-report.https.sub.html");
+
+      // All credentials set at the top-level should be received.
+      const cookies = await pollCookies(endpoint, id);
+      assert_equals(cookies.samesite_none, "[samesite_none=reporting]", "Credential value was correct");
+      assert_equals(cookies.samesite_unspecified, "[samesite_unspecified=reporting]", "Credential value was correct");
+      assert_equals(cookies.samesite_lax, "[samesite_lax=reporting]", "Credential value was correct");
+      assert_equals(cookies.samesite_strict, "[samesite_strict=reporting]", "Credential value was correct");
+      assert_equals(Object.keys(cookies).length, 4, "No additional cookies were received");
+
+    }, "Reporting endpoints received credentials.");
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/reporting/resources/middle-frame.https.sub.html b/third_party/blink/web_tests/external/wpt/reporting/resources/middle-frame.https.sub.html
new file mode 100644
index 0000000..0dd26ec
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/reporting/resources/middle-frame.https.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Utility page which embeds a reporting page on the chosen host</title>
+</head>
+<body>
+  <script>
+      const searchParams = new URLSearchParams(window.location.search);
+      const host = searchParams.get('host') || "{{hosts[][]}}";
+      const frame = document.createElement('iframe');
+      frame.src=`https://${host}:{{ports[https][0]}}/reporting/resources/same-origin-report.https.sub.html`;
+      document.body.appendChild(frame);
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/reporting/resources/same-origin-report.https.sub.html b/third_party/blink/web_tests/external/wpt/reporting/resources/same-origin-report.https.sub.html
new file mode 100644
index 0000000..326a0fd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/reporting/resources/same-origin-report.https.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Generates a CSP violation report and sends it to a same-origin endpoint</title>
+</head>
+<body>
+  <script>
+        const img = document.createElement('img');
+        img.src = "/reporting/resources/fail.png";
+        document.body.appendChild(img);
+        // Post back to the main frame that the report should have been queued.
+        top.postMessage("done", "*");
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/reporting/resources/same-origin-report.https.sub.html.sub.headers b/third_party/blink/web_tests/external/wpt/reporting/resources/same-origin-report.https.sub.html.sub.headers
new file mode 100644
index 0000000..8244fafb2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/reporting/resources/same-origin-report.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID=d0d517bf-891b-457a-b970-8b2b2c81a0bf"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint
diff --git a/third_party/blink/web_tests/external/wpt/reporting/same-origin-cross-site-credentials.https.sub.html b/third_party/blink/web_tests/external/wpt/reporting/same-origin-cross-site-credentials.https.sub.html
new file mode 100644
index 0000000..258ab8e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/reporting/same-origin-cross-site-credentials.https.sub.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that credentials are sent properly in a same-origin but not same-site context</title>
+  <script src='/https/chromium.googlesource.com/resources/testharness.js'></script>
+  <script src='/https/chromium.googlesource.com/resources/testharnessreport.js'></script>
+  <script src='resources/report-helper.js'></script>
+</head>
+<body>
+  <script>
+    const base_url = `${location.protocol}//${location.host}`;
+    const endpoint = `${base_url}/reporting/resources/report.py`;
+    const id = 'd0d517bf-891b-457a-b970-8b2b2c81a0bf';
+
+    promise_test(async t => {
+      // If this is not run from the expected origin, then the A->B->A frame embedding will not be correct,
+      // and the cookies set in the top-level page will never be returned with the reports.
+      assert_true(location.href.startsWith("https://{{hosts[][]}}:{{ports[https][0]}}/"),
+                  "Test running on unexpected origin; subsequent assertions will fail.");
+
+      // Set credentials, and set up test to clear them afterwards.
+      await fetch('/cookies/resources/setSameSite.py?reporting', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+      t.add_cleanup(() => fetch("/cookies/resources/dropSameSite.py", {mode: 'no-cors', credentials: 'include', cache: 'no-store'}));
+
+      // Insert a cross-origin frame which will then frame this origin to
+      // trigger a CSP error.
+      const frame = document.createElement('iframe');
+      frame.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/reporting/resources/middle-frame.https.sub.html";
+      document.body.appendChild(frame);
+
+      // Wait for the inner frame to signal that the report has been generated.
+      await new Promise(resolve => {
+        window.addEventListener('message', ev => {
+          if (ev.data === "done")
+            resolve(ev.data);
+          });
+        document.body.appendChild(frame);
+      });
+
+      const reports = await pollReports(endpoint, id);
+      checkReportExists(reports, 'csp-violation', "https://{{hosts[][]}}:{{ports[https][0]}}/reporting/resources/same-origin-report.https.sub.html");
+
+      // Same-site: None cookies should be sent, but not Lax, Strict, or default cookies.
+      const cookies = await pollCookies(endpoint, id);
+      assert_equals(cookies.samesite_none, "[samesite_none=reporting]", "Credential value was correct");
+      assert_false("samesite_strict" in cookies, "Same-site: Strict cookies should not be sent");
+      assert_false("samesite_lax" in cookies, "Same-site: Lax cookies should not be sent");
+      assert_false("samesite_unspecified" in cookies, "Same-site unspecified cookies should not be sent");
+      assert_equals(Object.keys(cookies).length, 1, "No additional cookies were received");
+    }, "Reporting endpoints received credentials.");
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/reporting/same-origin-same-site-credentials.https.sub.html b/third_party/blink/web_tests/external/wpt/reporting/same-origin-same-site-credentials.https.sub.html
new file mode 100644
index 0000000..9b99edb2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/reporting/same-origin-same-site-credentials.https.sub.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that credentials are sent properly in a same-origin and also same-site nested context</title>
+  <script src='/https/chromium.googlesource.com/resources/testharness.js'></script>
+  <script src='/https/chromium.googlesource.com/resources/testharnessreport.js'></script>
+  <script src='resources/report-helper.js'></script>
+</head>
+<body>
+  <script>
+    const base_url = `${location.protocol}//${location.host}`;
+    const endpoint = `${base_url}/reporting/resources/report.py`;
+    const id = 'd0d517bf-891b-457a-b970-8b2b2c81a0bf';
+
+    promise_test(async t => {
+      // If this is not run from the expected origin, then the A->A->A frame embedding will not be correct,
+      // and the cookies set in the top-level page will never be returned with the reports.
+      assert_true(location.href.startsWith("https://{{hosts[][]}}:{{ports[https][0]}}/"),
+                  "Test running on unexpected origin; subsequent assertions will fail.");
+
+      // Set credentials, and set up test to clear them afterwards.
+      await fetch('/cookies/resources/setSameSite.py?reporting', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+      t.add_cleanup(() => fetch("/cookies/resources/dropSameSite.py", {mode: 'no-cors', credentials: 'include', cache: 'no-store'}));
+
+      // Insert a same-origin frame which will then frame this origin to
+      // trigger a CSP error.
+      const frame = document.createElement('iframe');
+      frame.src = "https://{{hosts[][]}}:{{ports[https][0]}}/reporting/resources/middle-frame.https.sub.html";
+      document.body.appendChild(frame);
+
+      // Wait for the inner frame to signal that the report has been generated.
+      await new Promise(resolve => {
+        window.addEventListener('message', ev => {
+          if (ev.data === "done")
+            resolve(ev.data);
+          });
+        document.body.appendChild(frame);
+      });
+
+      const reports = await pollReports(endpoint, id);
+      checkReportExists(reports, 'csp-violation', "https://{{hosts[][]}}:{{ports[https][0]}}/reporting/resources/same-origin-report.https.sub.html");
+
+      // All credentials set at the top-level should be received.
+      const cookies = await pollCookies(endpoint, id);
+      assert_equals(cookies.samesite_none, "[samesite_none=reporting]", "Credential value was correct");
+      assert_equals(cookies.samesite_unspecified, "[samesite_unspecified=reporting]", "Credential value was correct");
+      assert_equals(cookies.samesite_lax, "[samesite_lax=reporting]", "Credential value was correct");
+      assert_equals(cookies.samesite_strict, "[samesite_strict=reporting]", "Credential value was correct");
+      assert_equals(Object.keys(cookies).length, 4, "No additional cookies were received");
+    }, "Reporting endpoints received credentials.");
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/cross-origin-report-no-credentials-report-to.https.sub.html b/third_party/blink/web_tests/wpt_internal/reporting/cross-origin-report-no-credentials-report-to.https.sub.html
new file mode 100644
index 0000000..d4ff60f3
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/reporting/cross-origin-report-no-credentials-report-to.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that reports are sent without credentials to cross-origin endpoints</title>
+  <script src='/https/chromium.googlesource.com/resources/testharness.js'></script>
+  <script src='/https/chromium.googlesource.com/resources/testharnessreport.js'></script>
+  <script src='resources/report-helper.js'></script>
+</head>
+<body>
+  <script>
+    const base_url = `${location.protocol}//${location.host}`;
+    const endpoint = `${base_url}/reporting/resources/report.py`;
+    const id = 'd0d517bf-891b-457a-b970-8b2b2c81a0bf';
+
+    promise_test(async t => {
+      // Set credentials, and set up test to clear them afterwards.
+      await fetch('/cookies/resources/set-cookie.py?name=report&path=%2F', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+      t.add_cleanup(() => fetch("/cookies/resources/set.py?report=; path=%2F; expires=Thu, 01 Jan 1970 00:00:01 GMT"));
+
+      // Trigger a CSP error.
+      await new Promise(resolve => {
+        const img = document.createElement('img');
+        img.src = "/reporting/resources/fail.png";
+        img.addEventListener('error', resolve);
+        document.body.appendChild(img);
+      });
+
+      // Wait for report to be received.
+      await wait(3000);
+      const reports = await pollReports(endpoint, id);
+      checkReportExists(reports, 'csp-violation', location.href);
+
+      // Validate that credentials were not sent to cross-origin endpoint.
+      const cookies = await pollCookies(endpoint, id);
+      assert_equals(Object.keys(cookies).length, 0, "Credentials were absent from report");
+    }, "Reporting endpoints did not receive credentials.");
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/cross-origin-report-no-credentials-report-to.https.sub.html.sub.headers b/third_party/blink/web_tests/wpt_internal/reporting/cross-origin-report-no-credentials-report-to.https.sub.html.sub.headers
new file mode 100644
index 0000000..93a84e9
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/reporting/cross-origin-report-no-credentials-report-to.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"https://{{domains[www1]}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=d0d517bf-891b-457a-b970-8b2b2c81a0bf","priority":1}]}
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/resources/report-helper.js b/third_party/blink/web_tests/wpt_internal/reporting/resources/report-helper.js
new file mode 100644
index 0000000..cc4f07f0
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/reporting/resources/report-helper.js
@@ -0,0 +1,38 @@
+function wait(ms) {
+  return new Promise(resolve => step_timeout(resolve, ms));
+}
+
+async function pollReports(endpoint, id, min_count) {
+  const res = await fetch(`${endpoint}?reportID=${id}${min_count ? `&min_count=${min_count}` : ''}`, {cache: 'no-store'});
+  const reports = [];
+  if (res.status === 200) {
+    for (const report of await res.json()) {
+      reports.push(report);
+    }
+  }
+  return reports;
+}
+
+async function pollCookies(endpoint, id) {
+  const res = await fetch(`${endpoint}?reportID=${id}&op=retrieve_cookies`, {cache: 'no-store'});
+  const dict = await res.json();
+  if (dict.reportCookies == 'None')
+    return {};
+  return dict.reportCookies;
+}
+
+async function pollNumResults(endpoint, id) {
+  const res = await fetch(`${endpoint}?reportID=${id}&op=retrieve_count`, {cache: 'no-store'});
+  const dict = await res.json();
+  if (dict.report_count == 'None')
+    return 0;
+  return JSON.parse(dict.report_count);
+}
+
+function checkReportExists(reports, type, url) {
+  for (const report of reports) {
+    if (report.type !== type) continue;
+    if (report.body.documentURL == url || report.body.sourceFile === url) return true;
+  }
+  assert_unreached(`A report of ${type} from ${url} is not found.`);
+}
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/resources/report.py b/third_party/blink/web_tests/wpt_internal/reporting/resources/report.py
new file mode 100644
index 0000000..244feb5b
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/reporting/resources/report.py
@@ -0,0 +1,147 @@
+import time
+import json
+import re
+import uuid
+
+from wptserve.utils import isomorphic_decode
+
+
+def retrieve_from_stash(request,
+                        key,
+                        timeout,
+                        default_value,
+                        min_count=None,
+                        retain=False):
+    """Retrieve the set of reports for a given report ID.
+
+  This will extract either the set of reports, credentials, or request count
+  from the stash (depending on the key passed in) and return it encoded as JSON.
+
+  When retrieving reports, this will not return any reports until min_count
+  reports have been received.
+
+  If timeout seconds elapse before the requested data can be found in the stash,
+  or before at least min_count reports are received, default_value will be
+  returned instead."""
+    t0 = time.time()
+    while time.time() - t0 < timeout:
+        time.sleep(0.5)
+        with request.server.stash.lock:
+            value = request.server.stash.take(key=key)
+            if value is not None:
+                have_sufficient_reports = (min_count is None
+                                           or len(value) >= min_count)
+                if retain or not have_sufficient_reports:
+                    request.server.stash.put(key=key, value=value)
+                if have_sufficient_reports:
+                    return json.dumps(value)
+
+    return default_value
+
+
+def main(request, response):
+    # Handle CORS preflight requests
+    if request.method == u'OPTIONS':
+        # Always reject preflights for one subdomain
+        if b"www2" in request.headers[b"Origin"]:
+            return (400, [], u"CORS preflight rejected for www2")
+        return [
+            (b"Content-Type", b"text/plain"),
+            (b"Access-Control-Allow-Origin", b"*"),
+            (b"Access-Control-Allow-Methods", b"post"),
+            (b"Access-Control-Allow-Headers", b"Content-Type"),
+        ], u"CORS allowed"
+
+    if b"reportID" in request.GET:
+        key = request.GET.first(b"reportID")
+    elif b"endpoint" in request.GET:
+        key = uuid.uuid5(uuid.NAMESPACE_OID,
+                         isomorphic_decode(
+                             request.GET[b'endpoint'])).urn.encode('ascii')[9:]
+    else:
+        response.status = 400
+        return "Either reportID or endpoint parameter is required."
+
+    # Cookie and count keys are derived from the report ID.
+    cookie_key = re.sub(b'^....', b'cccc', key)
+    count_key = re.sub(b'^....', b'dddd', key)
+
+    if request.method == u'GET':
+        try:
+            timeout = float(request.GET.first(b"timeout"))
+        except:
+            timeout = 0.5
+        try:
+            min_count = int(request.GET.first(b"min_count"))
+        except:
+            min_count = 1
+        retain = (b"retain" in request.GET)
+
+        op = request.GET.first(b"op", b"")
+        if op in (b"retrieve_report", b""):
+            return [(b"Content-Type", b"application/json")
+                    ], retrieve_from_stash(request, key, timeout, u'[]',
+                                           min_count, retain)
+
+        if op == b"retrieve_cookies":
+            return [(b"Content-Type", b"application/json")
+                    ], u"{ \"reportCookies\" : " + str(
+                        retrieve_from_stash(request, cookie_key, timeout,
+                                            u"\"None\"")) + u"}"
+
+        if op == b"retrieve_count":
+            return [(b"Content-Type", b"application/json")], json.dumps({
+                u'report_count':
+                str(retrieve_from_stash(request, count_key, timeout, 0))
+            })
+
+        response.status = 400
+        return "op parameter value not recognized."
+
+    # Save cookies.
+    if len(request.cookies.keys()) > 0:
+        # Convert everything into strings and dump it into a dict.
+        temp_cookies_dict = {}
+        for dict_key in request.cookies.keys():
+            temp_cookies_dict[isomorphic_decode(dict_key)] = str(
+                request.cookies.get_list(dict_key))
+        with request.server.stash.lock:
+            # Clear any existing cookie data for this request before storing new data.
+            request.server.stash.take(key=cookie_key)
+            request.server.stash.put(key=cookie_key, value=temp_cookies_dict)
+
+    # Append new report(s).
+    new_reports = json.loads(request.body)
+
+    # If the incoming report is a CSP report-uri report, then it will be a single
+    # dictionary rather than a list of reports. To handle this case, ensure that
+    # any non-list request bodies are wrapped in a list.
+    if not isinstance(new_reports, list):
+        new_reports = [new_reports]
+
+    for report in new_reports:
+        report[u"metadata"] = {
+            u"content_type":
+            isomorphic_decode(request.headers[b"Content-Type"]),
+        }
+
+    with request.server.stash.lock:
+        reports = request.server.stash.take(key=key)
+        if reports is None:
+            reports = []
+        reports.extend(new_reports)
+        request.server.stash.put(key=key, value=reports)
+
+    # Increment report submission count. This tracks the number of times this
+    # reporting endpoint was contacted, rather than the total number of reports
+    # submitted, which can be seen from the length of the report list.
+    with request.server.stash.lock:
+        count = request.server.stash.take(key=count_key)
+        if count is None:
+            count = 0
+        count += 1
+        request.server.stash.put(key=count_key, value=count)
+
+    # Return acknowledgement report.
+    return [(b"Content-Type", b"text/plain")
+            ], b"Recorded report " + request.body
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/same-origin-report-no-credentials-report-to.https.sub.html b/third_party/blink/web_tests/wpt_internal/reporting/same-origin-report-no-credentials-report-to.https.sub.html
new file mode 100644
index 0000000..158e3f3
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/reporting/same-origin-report-no-credentials-report-to.https.sub.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test that reports are sent without credentials to same-origin endpoints when using Report-To</title>
+  <script src='/https/chromium.googlesource.com/resources/testharness.js'></script>
+  <script src='/https/chromium.googlesource.com/resources/testharnessreport.js'></script>
+  <script src='resources/report-helper.js'></script>
+</head>
+<body>
+  <script>
+    const base_url = `${location.protocol}//${location.host}`;
+    const endpoint = `${base_url}/reporting/resources/report.py`;
+    const id = 'd0d517bf-891b-457a-b970-8b2b2c81a0bf';
+
+    promise_test(async t => {
+      // Set credentials, and set up test to clear them afterwards.
+      await fetch('/cookies/resources/set-cookie.py?name=report&path=%2F', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+      t.add_cleanup(() => fetch("/cookies/resources/set.py?report=; path=%2F; expires=Thu, 01 Jan 1970 00:00:01 GMT"));
+
+      // Trigger a CSP error.
+      await new Promise(resolve => {
+        const img = document.createElement('img');
+        img.src = "/reporting/resources/fail.png";
+        img.addEventListener('error', resolve);
+        document.body.appendChild(img);
+      });
+
+      // Wait for report to be received.
+      await wait(3000);
+      const reports = await pollReports(endpoint, id);
+      checkReportExists(reports, 'csp-violation', location.href);
+
+      // Validate that credentials were not sent to same-origin endpoint when
+      // configured with Report-To.
+      const cookies = await pollCookies(endpoint, id);
+      assert_equals(Object.keys(cookies).length, 0, "Credentials were absent from report");
+    }, "Reporting endpoints did not receive credentials.");
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/reporting/same-origin-report-no-credentials-report-to.https.sub.html.sub.headers b/third_party/blink/web_tests/wpt_internal/reporting/same-origin-report-no-credentials-report-to.https.sub.html.sub.headers
new file mode 100644
index 0000000..a8b81c0
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/reporting/same-origin-report-no-credentials-report-to.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Report-To: {"group":"csp-endpoint","max_age":10886400,"endpoints":[{"url":"/reporting/resources/report.py?reportID=d0d517bf-891b-457a-b970-8b2b2c81a0bf","priority":1}]}
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint