CVE-2026-87485
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifservices/network/cors/cors_url_loader.cc |
modified | |
CorsURLLoaderTestservices/network/cors/cors_url_loader_unittest.cc |
modified | |
CorsURLLoaderTestWithSafeRevalidationservices/network/cors/cors_url_loader_unittest.cc |
modified | |
CorsURLLoaderTestWithSafeRevalidationservices/network/cors/cors_url_loader_unittest.cc |
modified | |
BadMessageTestHelperservices/network/cors/cors_url_loader_unittest.cc |
modified | |
TEST_Fservices/network/cors/cors_url_loader_unittest.cc |
modified | |
TEST_Pservices/network/cors/cors_url_loader_unittest.cc |
modified |
Files Changed
services/network/cors/cors_url_loader.ccservices/network/cors/cors_url_loader_unittest.cc
Patch
From 2ef3dc14657fe82f7f6ce443e2367b80a14ae851 Mon Sep 17 00:00:00 2001 From: Takashi Toyoshima <[email protected]> Date: Mon, 03 Aug 2026 03:13:07 -0700 Subject: [PATCH] OOR-CORS: Safe revalidation handling via structured metadata This CL introduces a feature flag `kSafeRevalidation` (enabled by default) and refactors cache revalidation handling to prevent CORS bypass vulnerabilities. Instead of relying on Blink to directly set raw conditional headers (`If-None-Match`, `If-Modified-Since`) and self-report a spoofable `is_revalidating` flag, Blink now passes structured metadata (`revalidation_etag` and `revalidation_last_modified`) in `ResourceRequest`. When `kSafeRevalidation` is enabled, Network Service ignores the renderer-supplied `is_revalidating` flag and safely constructs conditional headers and handles 304 / CORS access checks based on the presence of structured revalidation metadata. Bug: 499230506 Change-Id: Ic390e9d17009df94ecde283e433b9891a5d6888c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8160285 Reviewed-by: Adam Rice <[email protected]> Commit-Queue: Takashi Toyoshima <[email protected]> Cr-Commit-Position: refs/heads/main@{#1672530} --- diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc index 33b672a..16ada06 100644 --- a/services/network/cors/cors_url_loader.cc +++ b/services/network/cors/cors_url_loader.cc @@ -75,6 +75,14 @@ kDisallowedHeader }; +bool IsRevalidatingRequest(const ResourceRequest& request) { + if (base::FeatureList::IsEnabled(features::kSafeRevalidation)) { + return request.revalidation_etag.has_value() || + request.revalidation_last_modified.has_value(); + } + return request.is_revalidating; +} + // Returns std::nullopt when a preflight isn't needed. Otherwise returns the // reason why a preflight is needed. std::optional<PreflightRequiredReason> NeedsPreflight( @@ -99,8 +107,13 @@ request.trusted_params && request.trusted_params->is_ad_auction_trusted_signals_request; + const bool is_revalidating_for_headers = + base::FeatureList::IsEnabled(features::kSafeRevalidation) + ? false + : request.is_revalidating; + if (!CorsUnsafeNotForbiddenRequestHeaderNames( - request.headers.GetHeaderVector(), request.is_revalidating, + request.headers.GetHeaderVector(), is_revalidating_for_headers, is_ad_auction_trusted_signals_request) .empty()) { return PreflightRequiredReason::kDisallowedHeader; @@ -611,7 +624,7 @@ // See 10.7.4 of https://fetch.spec.whatwg.org/#http-network-or-cache-fetch const bool is_304_for_revalidation = - request_.is_revalidating && response_head->headers && + IsRevalidatingRequest(request_) && response_head->headers && response_head->headers->response_code() == 304; if (fetch_cors_flag_ && !is_304_for_revalidation) { const auto result = CheckAccess( @@ -1115,6 +1128,17 @@ network_loader_start_time_ = base::TimeTicks::Now(); + if (base::FeatureList::IsEnabled(features::kSafeRevalidation)) { + if (request_.revalidation_etag) { + request_.headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch, + *request_.revalidation_etag); + } + if (request_.revalidation_last_modified) { + request_.headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince, + *request_.revalidation_last_modified); + } + } + if (sync_network_loader_factory_) { sync_network_loader_factory_->CreateLoaderAndStartWithSyncClient( network_loader_.BindNewPipeAndPassReceiver(), request_id_, options_, diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc index 41414ce..512a86f 100644 --- a/services/network/cors/cors_url_loader_unittest.cc +++ b/services/network/cors/cors_url_loader_unittest.cc @@ -54,6 +54,46 @@ class CorsURLLoaderTest : public CorsURLLoaderTestBase {}; +class CorsURLLoaderTestWithSafeRevalidation + : public CorsURLLoaderTestBase, + public testing::WithParamInterface<bool> { + public: + CorsURLLoaderTestWithSafeRevalidation() { + feature_list_.InitWithFeatureState(features::kSafeRevalidation, GetParam()); + } + + bool IsSafeRevalidationEnabled() const { return GetParam(); } + + void SetRevalidationMetadata(ResourceRequest& request, + const std::string& etag, + const std::string& last_modified) { + if (IsSafeRevalidationEnabled()) { + if (!etag.empty()) { + request.revalidation_etag = etag; + } + if (!last_modified.empty()) { + request.revalidation_last_modified = last_modified; + } + } else { + request.is_revalidating = true; + if (!etag.empty()) { + request.headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch, etag); + } + if (!last_modified.empty()) { + request.headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince, + last_modified); + } + } + } + + private: + base::test::ScopedFeatureList feature_list_; +}; + +INSTANTIATE_TEST_SUITE_P(All, + CorsURLLoaderTestWithSafeRevalidation, + testing::Bool()); + class BadMessageTestHelper { public: BadMessageTestHelper() @@ -1838,10 +1878,9 @@ url::Origin::Create(origin).Serialize()); } -TEST_F(CorsURLLoaderTest, 304ForSimpleRevalidation) { +TEST_P(CorsURLLoaderTestWithSafeRevalidation, 304ForSimpleRevalidation) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); - const GURL new_url("https://other2.example.com/bar.png"); ResourceRequest request; request.mode = mojom::RequestMode::kCors; @@ -1849,10 +1888,7 @@ request.method = "GET"; request.url = url; request.request_initiator = url::Origin::Create(origin); - request.headers.SetHeader("If-Modified-Since", "x"); - request.headers.SetHeader("If-None-Match", "y"); - request.headers.SetHeader("Cache-Control", "z"); - request.is_revalidating = true; + SetRevalidationMetadata(request, "y", "x"); CreateLoaderAndStart(request); RunUntilCreateLoaderAndStartCalled(); @@ -1924,10 +1960,9 @@ EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code); } -TEST_F(CorsURLLoaderTest, RevalidationAndPreflight) { +TEST_P(CorsURLLoaderTestWithSafeRevalidation, RevalidationAndPreflight) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); - const GURL new_url("https://other2.example.com/bar.png"); ResourceRequest original_request; original_request.mode = mojom::RequestMode::kCors; @@ -1935,11 +1970,8 @@ original_request.method = "GET"; original_request.url = url; original_request.request_initiator = url::Origin::Create(origin); - original_request.headers.SetHeader("If-Modified-Since", "x"); - original_request.headers.SetHeader("If-None-Match", "y"); - original_request.headers.SetHeader("Cache-Control", "z"); + SetRevalidationMetadata(original_request, "y", "x"); original_request.headers.SetHeader("foo", "bar"); - original_request.is_revalidating = true; CreateLoaderAndStart(original_request); RunUntilCreateLoaderAndStartCalled(); @@ -1959,6 +1991,9 @@ EXPECT_EQ(2, num_created_loaders()); EXPECT_EQ(GetRequest().url, url); EXPECT_EQ(GetRequest().method, "GET"); + EXPECT_EQ(GetRequest().headers.GetHeader("If-Modified-Since"), "x"); + EXPECT_EQ(GetRequest().headers.GetHeader("If-None-Match"), "y"); + EXPECT_EQ(GetRequest().headers.GetHeader("foo"), "bar"); NotifyLoaderClientOnReceiveResponse( {{"Access-Control-Allow-Origin", "https://example.com"}}); @@ -3417,5 +3452,63 @@
Regression Test / PoC
diff --git a/services/network/cors/cors_url_loader_unittest.cc b/services/network/cors/cors_url_loader_unittest.cc
index 41414ce..512a86f 100644
--- a/services/network/cors/cors_url_loader_unittest.cc
+++ b/services/network/cors/cors_url_loader_unittest.cc
@@ -54,6 +54,46 @@
class CorsURLLoaderTest : public CorsURLLoaderTestBase {};
+class CorsURLLoaderTestWithSafeRevalidation
+ : public CorsURLLoaderTestBase,
+ public testing::WithParamInterface<bool> {
+ public:
+ CorsURLLoaderTestWithSafeRevalidation() {
+ feature_list_.InitWithFeatureState(features::kSafeRevalidation, GetParam());
+ }
+
+ bool IsSafeRevalidationEnabled() const { return GetParam(); }
+
+ void SetRevalidationMetadata(ResourceRequest& request,
+ const std::string& etag,
+ const std::string& last_modified) {
+ if (IsSafeRevalidationEnabled()) {
+ if (!etag.empty()) {
+ request.revalidation_etag = etag;
+ }
+ if (!last_modified.empty()) {
+ request.revalidation_last_modified = last_modified;
+ }
+ } else {
+ request.is_revalidating = true;
+ if (!etag.empty()) {
+ request.headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch, etag);
+ }
+ if (!last_modified.empty()) {
+ request.headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince,
+ last_modified);
+ }
+ }
+ }
+
+ private:
+ base::test::ScopedFeatureList feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+ CorsURLLoaderTestWithSafeRevalidation,
+ testing::Bool());
+
class BadMessageTestHelper {
public:
BadMessageTestHelper()
@@ -1838,10 +1878,9 @@
url::Origin::Create(origin).Serialize());
}
-TEST_F(CorsURLLoaderTest, 304ForSimpleRevalidation) {
+TEST_P(CorsURLLoaderTestWithSafeRevalidation, 304ForSimpleRevalidation) {
const GURL origin("https://example.com");
const GURL url("https://other.example.com/foo.png");
- const GURL new_url("https://other2.example.com/bar.png");
ResourceRequest request;
request.mode = mojom::RequestMode::kCors;
@@ -1849,10 +1888,7 @@
request.method = "GET";
request.url = url;
request.request_initiator = url::Origin::Create(origin);
- request.headers.SetHeader("If-Modified-Since", "x");
- request.headers.SetHeader("If-None-Match", "y");
- request.headers.SetHeader("Cache-Control", "z");
- request.is_revalidating = true;
+ SetRevalidationMetadata(request, "y", "x");
CreateLoaderAndStart(request);
RunUntilCreateLoaderAndStartCalled();
@@ -1924,10 +1960,9 @@
EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code);
}
-TEST_F(CorsURLLoaderTest, RevalidationAndPreflight) {
+TEST_P(CorsURLLoaderTestWithSafeRevalidation, RevalidationAndPreflight) {
const GURL origin("https://example.com");
const GURL url("https://other.example.com/foo.png");
- const GURL new_url("https://other2.example.com/bar.png");
ResourceRequest original_request;
original_request.mode = mojom::RequestMode::kCors;
@@ -1935,11 +1970,8 @@
original_request.method = "GET";
original_request.url = url;
original_request.request_initiator = url::Origin::Create(origin);
- original_request.headers.SetHeader("If-Modified-Since", "x");
- original_request.headers.SetHeader("If-None-Match", "y");
- original_request.headers.SetHeader("Cache-Control", "z");
+ SetRevalidationMetadata(original_request, "y", "x");
original_request.headers.SetHeader("foo", "bar");
- original_request.is_revalidating = true;
CreateLoaderAndStart(original_request);
RunUntilCreateLoaderAndStartCalled();
@@ -1959,6 +1991,9 @@
EXPECT_EQ(2, num_created_loaders());
EXPECT_EQ(GetRequest().url, url);
EXPECT_EQ(GetRequest().method, "GET");
+ EXPECT_EQ(GetRequest().headers.GetHeader("If-Modified-Since"), "x");
+ EXPECT_EQ(GetRequest().headers.GetHeader("If-None-Match"), "y");
+ EXPECT_EQ(GetRequest().headers.GetHeader("foo"), "bar");
NotifyLoaderClientOnReceiveResponse(
{{"Access-Control-Allow-Origin", "https://example.com"}});
@@ -3417,5 +3452,63 @@
VerifyUpdateRequestForRedirect(303, "FOO");
}
+TEST_F(CorsURLLoaderTest, SafeRevalidationIgnoresSpoofedIsRevalidating) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(features::kSafeRevalidation);
+
+ const GURL origin("https://example.com");
+ const GURL url("https://other.example.com/secret");
+
+ ResourceRequest request;
+ request.mode = mojom::RequestMode::kCors;
+ request.credentials_mode = mojom::CredentialsMode::kOmit;
+ request.method = "GET";
+ request.url = url;
+ request.request_initiator = url::Origin::Create(origin);
+ request.is_revalidating = true; // Spoofed flag without metadata!
+
+ CreateLoaderAndStart(request);
+ RunUntilCreateLoaderAndStartCalled();
+
+ // 304 response without Access-Control-Allow-Origin header
+ NotifyLoaderClientOnReceiveResponse(304, {});
+ NotifyLoaderClientOnComplete(net::OK);
+ RunUntilComplete();
+
+ EXPECT_TRUE(client().has_received_completion());
+ EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code);
+}
+
+TEST_F(CorsURLLoaderTest, SafeRevalidationWithStructuredMetadata) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitAndEnableFeature(features::kSafeRevalidation);
+
+ const GURL origin("https://example.com");
+ const GURL url("https://other.example.com/secret");
+
+ ResourceRequest request;
+ request.mode = mojom::RequestMode::kCors;
+ request.credentials_mode = mojom::CredentialsMode::kOmit;
+ request.method = "GET";
+ request.url = url;
+ request.request_initiator = url::Origin::Create(origin);
+ request.revalidation_etag = "\"my-etag\"";
+
+ CreateLoaderAndStart(request);
+ RunUntilCreateLoaderAndStartCalled();
+
+ EXPECT_TRUE(IsNetworkLoaderStarted());
+ EXPECT_EQ(GetRequest().headers.GetHeader("If-None-Match"), "\"my-etag\"");
+
+ // 304 response without ACAO should pass when safe revalidation metadata is
+ // present
+ NotifyLoaderClientOnReceiveResponse(304, {});
+ NotifyLoaderClientOnComplete(net::OK);
+ RunUntilComplete();
+
+ EXPECT_TRUE(client().has_received_completion());
+ EXPECT_EQ(net::OK, client().completion_status().error_code);
+}
+
} // namespace
} // namespace network::cors
diff --git a/services/network/public/cpp/url_request_mojom_traits_unittest.cc b/services/network/public/cpp/url_request_mojom_traits_unittest.cc
index c3d76ae..41825ea 100644
--- a/services/network/public/cpp/url_request_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/url_request_mojom_traits_unittest.cc
@@ -38,7 +38,9 @@
namespace network {
namespace {
-TEST(URLRequestMojomTraitsTest, Roundtrips_URLRequestReferrerPolicy) {
+using URLRequestMojomTraitsTest = testing::Test;
+
+TEST_F(URLRequestMojomTraitsTest, Roundtrips_URLRequestReferrerPolicy) {
for (auto referrer_policy :
{net::ReferrerPolicy::CLEAR_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
net::ReferrerPolicy::REDUCE_GRANULARITY_ON_TRANSITION_CROSS_ORIGIN,
@@ -59,7 +61,7 @@
}
}
-TEST(URLRequestMojomTraitsTest, Roundtrips_ResourceRequest) {
+TEST_F(URLRequestMojomTraitsTest, Roundtrips_ResourceRequest) {
network::ResourceRequest original;
original.method = "POST";
original.url = GURL("https://example.com/resources/dummy.xml");
@@ -181,7 +183,61 @@
EXPECT_TRUE(original.EqualsForTesting(copied));
}
-TEST(URLRequestMojomTraitsTest, Roundtrips_TrustedParams) {
+TEST_F(URLRequestMojomTraitsTest,
+ Roundtrips_ResourceRequestWithRevalidationMetadata) {
+ network::ResourceRequest original;
+ original.url = GURL("https://example.com/");
+ original.revalidation_etag = "\"12345\"";
+ original.revalidation_last_modified = "Wed, 21 Oct 2015 07:28:00 GMT";
+
+ network::ResourceRequest copied;
+ EXPECT_TRUE(
+ mojo::test::SerializeAndDeserialize<mojom::URLRequest>(original, copied));
+ EXPECT_EQ(copied.revalidation_etag, "\"12345\"");
+ EXPECT_EQ(copied.revalidation_last_modified, "Wed, 21 Oct 2015 07:28:00 GMT");
+}
+
+TEST_F(URLRequestMojomTraitsTest, RevalidationMetadata_InvalidValues) {
+ {
+ // Empty etag should fail deserialization
+ network::ResourceRequest original;
+ original.url = GURL("https://example.com/");
+ original.revalidation_etag = "";
+ network::ResourceRequest copied;
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::URLRequest>(
+ original, copied));
+ }
+ {
+ // Invalid header value (containing CRLF) in etag should fail
+ // deserialization
+ network::ResourceRequest original;
+ original.url = GURL("https://example.com/");
+ original.revalidation_etag = "invalid\r\nheader";
+ network::ResourceRequest copied;
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::URLRequest>(
+ original, copied));
+ }
+ {
+ // Empty last_modified should fail deserialization
+ network::ResourceRequest original;
+ original.url = GURL("https://example.com/");
+ original.revalidation_last_modified = "";
+ network::ResourceRequest copied;
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::URLRequest>(
+ original, copied));
+ }
+ {
+ // Invalid header value in last_modified should fail deserialization
+ network::ResourceRequest original;
+ original.url = GURL("https://example.com/");
+ original.revalidation_last_modified = "invalid\r\nheader";
+ network::ResourceRequest copied;
+ EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::URLRequest>(
+ original, copied));
+ }
+}
+
+TEST_F(URLRequestMojomTraitsTest, Roundtrips_TrustedParams) {
network::ResourceRequest::TrustedParams original;
original.disable_secure_dns = true;
original.allow_cookies_from_browser = true;
@@ -227,7 +283,7 @@
EXPECT_TRUE(copied.response_body_stream->pipe.is_valid());
}
-TEST(URLRequestMojomTraitsTest, Roundtrips_TrustedParams_NullOpt) {
+TEST_F(URLRequestMojomTraitsTest, Roundtrips_TrustedParams_NullOpt) {
network::ResourceRequest::TrustedParams original;
original.enabled_client_hints = std::nullopt;
network::ResourceRequest::TrustedParams copied;
Original Bug Report
CORS bypass and cross-origin data leak via spoofed is_revalidating flag
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.
Overview: A compromised renderer can bypass CORS checks by setting the internal is_revalidating flag to true on a resource request. This allows an attacker to send cache validation headers cross-origin without a preflight and bypass access checks on 304 Not Modified responses. Consequently, the attacker can leak cross-origin headers and create credentialed ETag/Last-Modified oracles.
Affected files:
services/network/cors/cors_url_loader.ccservices/network/cors/cors_url_loader_factory.ccservices/network/public/cpp/cors/cors_util.ccservices/network/public/mojom/url_request.mojom
Estimated timestamp from git blame: 2026-02-19
Description
There is a potential vulnerability in the Network Service’s handling of CORS checks for cache revalidation requests. The network::mojom::URLRequest struct contains a boolean flag is_revalidating. This flag is populated by the renderer and passed to the Network Service via Mojo IPC.
Currently, the Network Service implicitly trusts this renderer-provided flag to relax CORS enforcements:
- Preflight Bypass: In
CorsUnsafeNotForbiddenRequestHeaderNames(services/network/cors/cors_util.cc), ifis_revalidatingis true, cache validation headers likeIf-Modified-SinceandIf-None-Matchare exempted from triggering a CORS preflight. - Access Check Bypass: In
CorsURLLoader::OnReceiveResponse(services/network/cors/cors_url_loader.cc), ifis_revalidatingis true and the server responds with an HTTP 304 (Not Modified), the loader setsis_304_for_revalidation = trueand skips theCheckAccess()function entirely.
Because a compromised renderer process can arbitrarily construct Mojo IPC messages, an attacker can spoof this flag on a cross-origin, credentialed request. By doing so, they can force the Network Service to skip both the preflight request and the response access checks for 304 responses, resulting in the raw URLResponseHead (including all cross-origin HTTP headers) being forwarded back to the compromised renderer. This acts as a cross-origin information leak and an ETag/Last-Modified oracle.
Potential Reproduction Steps
Note: These are suggested theoretical steps to trigger the vulnerability. Our tooling agent does not actively execute code or PoCs.
- Compromise a Renderer: An attacker achieves arbitrary code execution within a sandboxed renderer process (e.g., via a V8 bug).
- Construct a Malicious Request: Using the
network::mojom::URLLoaderFactoryIPC interface, the attacker creates anetwork::ResourceRequesttargeting a sensitive cross-origin endpoint (e.g.,https://victim.example/api/user-data). - Configure CORS and Credentials: The attacker sets
mode = kCorsandcredentials_mode = kInclude. - Inject Cache Headers and Spoof Flag: The attacker adds an
If-None-Match: "target-etag"header and sets theis_revalidatingboolean flag totrue. - Initiate Request: The attacker sends the
CreateLoaderAndStartIPC message. - Network Service Bypasses Preflight: The Network Service receives the request. Due to the spoofed
is_revalidatingflag, it skips the preflight requirement for theIf-None-Matchheader. - Server Responds: The victim server receives the credentialed GET request. If the ETag matches, it returns an HTTP
304 Not Modified(which typically lacksAccess-Control-Allow-Originheaders). - Network Service Bypasses Access Check: In
CorsURLLoader::OnReceiveResponse, the network service seesis_revalidating == trueandresponse_code == 304. It skips theCheckAccessevaluation. - Data Leak: The Network Service forwards the
URLResponseHeadcontaining the raw cross-origin headers back to the renderer, allowing the attacker to read them and confirm the ETag state.
Suggested Fix
The Network Service should not rely solely on the untrusted, renderer-provided is_revalidating flag to bypass critical CORS security checks.
Potential remediation approaches:
- Enforce Server-Side Tracking: The Network Service (e.g., the HTTP Cache layer) should track whether a request is genuinely an internal cache revalidation, rather than accepting the state directly from the IPC struct. The renderer should not dictate this state.
- Header Filtering: If the renderer must control this flag,
CorsURLLoadershould strip sensitive/non-safelisted cross-origin headers from the304response before forwarding theURLResponseHeadback to the untrusted renderer whenCheckAccessis skipped.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.