Medium chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Privacy
DescriptionImproper input validation in Privacy
ComponentPrivacy
Sub-componentBlink>EnumerateDevices, Blink>GetUserMedia, Privacy
Bug ClassInput Validation
Tracker518055862
Fix commitb51618bbc9eb (chromium/src) +129/-9
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
IN_PROC_BROWSER_TEST_F
chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
modified

Files Changed

  • chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
  • chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
  • chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
From b51618bbc9eb090a615cf2c30c35a4a0bb3b00e2 Mon Sep 17 00:00:00 2001
From: Guido Urdaneta <guidou@chromium.org>
Date: Thu, 17 Sep 2026 10:22:15 -0700
Subject: [PATCH] Preserve media device salts during partitioned cookie deletion

When clearing browsing data with a filter configured for partitioned
cookies only (such as via Clear-Site-Data in partitioned contexts or
bounce tracking mitigations), ChromeBrowsingDataRemoverDelegate invoked
MediaDeviceSaltService::DeleteSalts() without verifying the partition
scope. Because storage key domain matching can match first-party
storage keys, unpartitioned media device salts were inadvertently
deleted.

This CL updates ChromeBrowsingDataRemoverDelegate to check
filter_builder->PartitionedCookiesOnly() before invoking DeleteSalts,
aligning it with other embedder cookie data handlers and preserving
media device salts when only partitioned cookies are cleared.

Fix taken from https://crbug.com/518055862#comment6

Bug: 518055862
Change-Id: Id4bbff688d91313151162c1a9e8aed2f8b45c387
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8412570
Reviewed-by: Christian Dullweber <dullweber@chromium.org>
Commit-Queue: Guido Urdaneta <guidou@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1699935}
---
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index bdb99adb..daf4922 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -671,16 +671,19 @@
         delegate->ClearPersistedTokens();
     }
 
-    if (auto* media_device_salt_service =
-            MediaDeviceSaltServiceFactory::GetInstance()->GetForBrowserContext(
-                profile_)) {
-      content::StoragePartition::StorageKeyMatcherFunction storage_key_matcher;
-      if (!filter_builder->MatchesAllOriginsAndDomains()) {
-        storage_key_matcher = filter_builder->BuildStorageKeyFilter();
+    if (!filter_builder->PartitionedCookiesOnly()) {
+      if (auto* media_device_salt_service =
+              MediaDeviceSaltServiceFactory::GetInstance()
+                  ->GetForBrowserContext(profile_)) {
+        content::StoragePartition::StorageKeyMatcherFunction
+            storage_key_matcher;
+        if (!filter_builder->MatchesAllOriginsAndDomains()) {
+          storage_key_matcher = filter_builder->BuildStorageKeyFilter();
+        }
+        media_device_salt_service->DeleteSalts(
+            delete_begin_, delete_end_, std::move(storage_key_matcher),
+            CreateTaskCompletionClosure(TracingDataType::kMediaDeviceSalts));
       }
-      media_device_salt_service->DeleteSalts(
-          delete_begin_, delete_end_, std::move(storage_key_matcher),
-          CreateTaskCompletionClosure(TracingDataType::kMediaDeviceSalts));
     }
 
 #if !BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
index 40f882d6..f804e3d18 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
@@ -441,6 +441,83 @@
   EXPECT_NE(original_salt, new_salt);
 }
 
+// Test that the salt for media device IDs is not reset when only partitioned
+// cookies are cleared.
+IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
+                       MediaDeviceIdSalt_PartitionedCookiesOnly) {
+  GURL url = embedded_test_server()->GetURL(kExampleHost, "/simple_page.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
+
+  content::RenderFrameHost* frame_host = GetBrowser()
+                                             ->tab_strip_model()
+                                             ->GetActiveWebContents()
+                                             ->GetPrimaryMainFrame();
+  url::Origin origin = frame_host->GetLastCommittedOrigin();
+  net::SiteForCookies site_for_cookies =
+      net::SiteForCookies::FromOrigin(origin);
+  blink::StorageKey storage_key = blink::StorageKey::CreateFirstParty(origin);
+
+  base::test::TestFuture<bool, const std::string&> future;
+  content::GetContentClientForTesting()->browser()->GetMediaDeviceIDSalt(
+      frame_host, site_for_cookies, storage_key, future.GetCallback());
+  std::string original_salt = future.Get<1>();
+
+  std::unique_ptr<content::BrowsingDataFilterBuilder> filter_builder =
+      content::BrowsingDataFilterBuilder::Create(
+          content::BrowsingDataFilterBuilder::Mode::kDelete);
+  filter_builder->AddRegisterableDomain(kExampleHost);
+  filter_builder->SetCookiePartitionKeyCollection(
+      net::CookiePartitionKeyCollection(
+          net::CookiePartitionKey::FromURLForTesting(
+              GURL("https://other-site.com"))));
+  filter_builder->SetPartitionedCookiesOnly(true);
+
+  RemoveWithFilterAndWait(content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+                          std::move(filter_builder));
+
+  future.Clear();
+  content::GetContentClientForTesting()->browser()->GetMediaDeviceIDSalt(
+      frame_host, site_for_cookies, storage_key, future.GetCallback());
+  std::string new_salt = future.Get<1>();
+
+  EXPECT_EQ(original_salt, new_salt);
+}
+
+// Test that Clear-Site-Data for partitioned cookies does not reset the
+// first-party salt for media device IDs.
+IN_PROC_BROWSER_TEST_F(
+    BrowsingDataRemoverBrowserTest,
+    ClearSiteDataPartitionedCookiesDoesNotResetMediaDeviceIdSalt) {
+  GURL url = embedded_test_server()->GetURL(kExampleHost, "/simple_page.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
+
+  content::RenderFrameHost* frame_host = GetBrowser()
+                                             ->tab_strip_model()
+                                             ->GetActiveWebContents()
+                                             ->GetPrimaryMainFrame();
+  url::Origin origin = frame_host->GetLastCommittedOrigin();
+  net::SiteForCookies site_for_cookies =
+      net::SiteForCookies::FromOrigin(origin);
+  blink::StorageKey storage_key = blink::StorageKey::CreateFirstParty(origin);
+
+  base::test::TestFuture<bool, const std::string&> future;
+  content::GetContentClientForTesting()->browser()->GetMediaDeviceIDSalt(
+      frame_host, site_for_cookies, storage_key, future.GetCallback());
+  std::string original_salt = future.Get<1>();
+
+  net::CookiePartitionKey partition_key =
+      net::CookiePartitionKey::FromURLForTesting(GURL("https://other.example"));
+  ClearSiteDataAndWait(origin, partition_key, /*storage_key=*/std::nullopt,
+                       /*storage_buckets_to_remove=*/{});
+
+  future.Clear();
+  content::GetContentClientForTesting()->browser()->GetMediaDeviceIDSalt(
+      frame_host, site_for_cookies, storage_key, future.GetCallback());
+  std::string new_salt = future.Get<1>();
+
+  EXPECT_EQ(original_salt, new_salt);
+}
+
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
 // Test that Sync is not paused when cookies are cleared.
 IN_PROC_BROWSER_TEST_F(DiceBrowsingDataRemoverBrowserTest, SyncToken) {
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 b959b16..a3f9f54 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
@@ -4605,6 +4605,46 @@
   EXPECT_EQ(salt3c, salt3b);
 }
 
+TEST_F(ChromeBrowsingDataRemoverDelegateMediaDeviceSaltTest,
+       PartitionedCookiesOnlyDoesNotRemoveSalts) {
+  std::string salt1 = GetSalt(StorageKey1());
+  std::string salt2 = GetSalt(StorageKey2());
+  std::string salt3 = GetSalt(StorageKey3());
+
+  std::unique_ptr<BrowsingDataFilterBuilder> filter(
+      BrowsingDataFilterBuilder::Create(
+          BrowsingDataFilterBuilder::Mode::kDelete));
+  filter->AddRegisterableDomain(StorageKey1().origin().host());
+  filter->SetCookiePartitionKeyCollection(net::CookiePartitionKeyCollection(
+      net::CookiePartitionKey::FromURLForTesting(
+          GURL("https://example2.com"))));
+  filter->SetPartitionedCookiesOnly(true);
+  BlockUntilOriginDataRemoved(base::Time(), base::Time::Max(),
+                              content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+                              std::move(filter));
+  EXPECT_EQ(GetSalt(StorageKey1()), salt1);
+  EXPECT_EQ(GetSalt(StorageKey2()), salt2);
+  EXPECT_EQ(GetSalt(StorageKey3()), salt3);
+}
+
+TEST_F(ChromeBrowsingDataRemoverDelegateMediaDeviceSaltTest,
+       PartitionedCookiesOnlyPreserveModeDoesNotRemoveSalts) {
+  std::string salt1 = GetSalt(StorageKey1());
+  std::string salt2 = GetSalt(StorageKey2());
+  std::string salt3 = GetSalt(StorageKey3());
+
+  std::unique_ptr<BrowsingDataFilterBuilder> filter(
+      BrowsingDataFilterBuilder::Create(
+          BrowsingDataFilterBuilder::Mode::kPreserve));
+  filter->SetPartitionedCookiesOnly(true);
+  BlockUntilOriginDataRemoved(base::Time(), base::Time::Max(),
+                              content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+                              std::move(filter));
+  EXPECT_EQ(GetSalt(StorageKey1()), salt1);
+  EXPECT_EQ(GetSalt(StorageKey2()), salt2);
+  EXPECT_EQ(GetSalt(StorageKey3()), salt3);
+}
+
 // Constants for ChromeBrowsingDataRemoverDelegateRelatedWebsiteSetsTest.
 namespace {
 constexpr char kPrimaryUrl[] = "https://subdomain.example.com:112";
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
index 40f882d6..f804e3d18 100644
--- a/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
+++ b/chrome/browser/browsing_data/browsing_data_remover_browsertest.cc
@@ -441,6 +441,83 @@
   EXPECT_NE(original_salt, new_salt);
 }
 
+// Test that the salt for media device IDs is not reset when only partitioned
+// cookies are cleared.
+IN_PROC_BROWSER_TEST_F(BrowsingDataRemoverBrowserTest,
+                       MediaDeviceIdSalt_PartitionedCookiesOnly) {
+  GURL url = embedded_test_server()->GetURL(kExampleHost, "/simple_page.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
+
+  content::RenderFrameHost* frame_host = GetBrowser()
+                                             ->tab_strip_model()
+                                             ->GetActiveWebContents()
+                                             ->GetPrimaryMainFrame();
+  url::Origin origin = frame_host->GetLastCommittedOrigin();
+  net::SiteForCookies site_for_cookies =
+      net::SiteForCookies::FromOrigin(origin);
+  blink::StorageKey storage_key = blink::StorageKey::CreateFirstParty(origin);
+
+  base::test::TestFuture<bool, const std::string&> future;
+  content::GetContentClientForTesting()->browser()->GetMediaDeviceIDSalt(
+      frame_host, site_for_cookies, storage_key, future.GetCallback());
+  std::string original_salt = future.Get<1>();
+
+  std::unique_ptr<content::BrowsingDataFilterBuilder> filter_builder =
+      content::BrowsingDataFilterBuilder::Create(
+          content::BrowsingDataFilterBuilder::Mode::kDelete);
+  filter_builder->AddRegisterableDomain(kExampleHost);
+  filter_builder->SetCookiePartitionKeyCollection(
+      net::CookiePartitionKeyCollection(
+          net::CookiePartitionKey::FromURLForTesting(
+              GURL("https://other-site.com"))));
+  filter_builder->SetPartitionedCookiesOnly(true);
+
+  RemoveWithFilterAndWait(content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+                          std::move(filter_builder));
+
+  future.Clear();
+  content::GetContentClientForTesting()->browser()->GetMediaDeviceIDSalt(
+      frame_host, site_for_cookies, storage_key, future.GetCallback());
+  std::string new_salt = future.Get<1>();
+
+  EXPECT_EQ(original_salt, new_salt);
+}
+
+// Test that Clear-Site-Data for partitioned cookies does not reset the
+// first-party salt for media device IDs.
+IN_PROC_BROWSER_TEST_F(
+    BrowsingDataRemoverBrowserTest,
+    ClearSiteDataPartitionedCookiesDoesNotResetMediaDeviceIdSalt) {
+  GURL url = embedded_test_server()->GetURL(kExampleHost, "/simple_page.html");
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(GetBrowser(), url));
+
+  content::RenderFrameHost* frame_host = GetBrowser()
+                                             ->tab_strip_model()
+                                             ->GetActiveWebContents()
+                                             ->GetPrimaryMainFrame();
+  url::Origin origin = frame_host->GetLastCommittedOrigin();
+  net::SiteForCookies site_for_cookies =
+      net::SiteForCookies::FromOrigin(origin);
+  blink::StorageKey storage_key = blink::StorageKey::CreateFirstParty(origin);
+
+  base::test::TestFuture<bool, const std::string&> future;
+  content::GetContentClientForTesting()->browser()->GetMediaDeviceIDSalt(
+      frame_host, site_for_cookies, storage_key, future.GetCallback());
+  std::string original_salt = future.Get<1>();
+
+  net::CookiePartitionKey partition_key =
+      net::CookiePartitionKey::FromURLForTesting(GURL("https://other.example"));
+  ClearSiteDataAndWait(origin, partition_key, /*storage_key=*/std::nullopt,
+                       /*storage_buckets_to_remove=*/{});
+
+  future.Clear();
+  content::GetContentClientForTesting()->browser()->GetMediaDeviceIDSalt(
+      frame_host, site_for_cookies, storage_key, future.GetCallback());
+  std::string new_salt = future.Get<1>();
+
+  EXPECT_EQ(original_salt, new_salt);
+}
+
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
 // Test that Sync is not paused when cookies are cleared.
 IN_PROC_BROWSER_TEST_F(DiceBrowsingDataRemoverBrowserTest, SyncToken) {
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 b959b16..a3f9f54 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
@@ -4605,6 +4605,46 @@
   EXPECT_EQ(salt3c, salt3b);
 }
 
+TEST_F(ChromeBrowsingDataRemoverDelegateMediaDeviceSaltTest,
+       PartitionedCookiesOnlyDoesNotRemoveSalts) {
+  std::string salt1 = GetSalt(StorageKey1());
+  std::string salt2 = GetSalt(StorageKey2());
+  std::string salt3 = GetSalt(StorageKey3());
+
+  std::unique_ptr<BrowsingDataFilterBuilder> filter(
+      BrowsingDataFilterBuilder::Create(
+          BrowsingDataFilterBuilder::Mode::kDelete));
+  filter->AddRegisterableDomain(StorageKey1().origin().host());
+  filter->SetCookiePartitionKeyCollection(net::CookiePartitionKeyCollection(
+      net::CookiePartitionKey::FromURLForTesting(
+          GURL("https://example2.com"))));
+  filter->SetPartitionedCookiesOnly(true);
+  BlockUntilOriginDataRemoved(base::Time(), base::Time::Max(),
+                              content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+                              std::move(filter));
+  EXPECT_EQ(GetSalt(StorageKey1()), salt1);
+  EXPECT_EQ(GetSalt(StorageKey2()), salt2);
+  EXPECT_EQ(GetSalt(StorageKey3()), salt3);
+}
+
+TEST_F(ChromeBrowsingDataRemoverDelegateMediaDeviceSaltTest,
+       PartitionedCookiesOnlyPreserveModeDoesNotRemoveSalts) {
+  std::string salt1 = GetSalt(StorageKey1());
+  std::string salt2 = GetSalt(StorageKey2());
+  std::string salt3 = GetSalt(StorageKey3());
+
+  std::unique_ptr<BrowsingDataFilterBuilder> filter(
+      BrowsingDataFilterBuilder::Create(
+          BrowsingDataFilterBuilder::Mode::kPreserve));
+  filter->SetPartitionedCookiesOnly(true);
+  BlockUntilOriginDataRemoved(base::Time(), base::Time::Max(),
+                              content::BrowsingDataRemover::DATA_TYPE_COOKIES,
+                              std::move(filter));
+  EXPECT_EQ(GetSalt(StorageKey1()), salt1);
+  EXPECT_EQ(GetSalt(StorageKey2()), salt2);
+  EXPECT_EQ(GetSalt(StorageKey3()), salt3);
+}
+
 // Constants for ChromeBrowsingDataRemoverDelegateRelatedWebsiteSetsTest.
 namespace {
 constexpr char kPrimaryUrl[] = "https://subdomain.example.com:112";
Loading diff…

Original Bug Report

reported by vm...@google.com

Deletion of first-party MediaDeviceSalts by Clear-Site-Data from partitioned contexts

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: Processing a Clear-Site-Data: “cookies” header in a partitioned third-party context potentially clears first-party MediaDeviceSalts. This occurs because ChromeBrowsingDataRemoverDelegate lacks a PartitionedCookiesOnly check when clearing media device salts, resulting in domain-scoped deletions matching first-party storage keys. This could allow third-party trackers to establish a cross-partition tracking channel using rotated persistent device IDs.

Affected files:

  • chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
  • content/browser/browsing_data/browsing_data_filter_builder_impl.cc

Estimated timestamp from git blame: 2023-07-21

Potential Root Cause Analysis

In ChromeBrowsingDataRemoverDelegate::RemoveEmbedderData (chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc), the deletion of media device salts is performed as part of the DATA_TYPE_COOKIES clearing block. However, unlike other sibling cookie deletions, it lacks a check for PartitionedCookiesOnly():

// chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
if (auto* media_device_salt_service =
        MediaDeviceSaltServiceFactory::GetInstance()->GetForBrowserContext(
            profile_)) {
  content::StoragePartition::StorageKeyMatcherFunction storage_key_matcher;
  if (!filter_builder->MatchesAllOriginsAndDomains()) {
    storage_key_matcher = filter_builder->BuildStorageKeyFilter();
  }
  media_device_salt_service->DeleteSalts(
      delete_begin_, delete_end_, std::move(storage_key_matcher),
      CreateTaskCompletionClosure(TracingDataType::kMediaDeviceSalts));
}

When a partitioned/3PCD-blocked third-party frame (e.g., https://tracker.test) embedded on https://publisher.test returns a Clear-Site-Data: "cookies" header, the browser initiates data clearing with a BrowsingDataFilterBuilder configured with PartitionedCookiesOnly(true).

Sibling cookie-clearing calls are protected via if (!filter_builder->PartitionedCookiesOnly()). However, the DeleteSalts block is executed unconditionally. Since no explicit StorageKey is configured on the cookie filter builder, BuildStorageKeyFilter() (in content/browser/browsing_data/browsing_data_filter_builder_impl.cc) returns a matcher function based on MatchesStorageKey.

This matcher uses StorageKey::MatchesRegistrableDomainForTrustedStorageDeletion(domain), which evaluates to true for any StorageKey whose top_level_site_ has a matching registrable domain. This matches the first-party StorageKey(https://tracker.test, top=https://tracker.test), meaning the tracker’s first-party media device salt is deleted instead of only partitioned entries.

Potential Steps to Reproduce

Note: The following are suggested/potential reproduction steps, as our testing environment does not currently support live code execution or browser automation.

  1. Navigate to https://tracker.test as a first-party site, grant camera/microphone permissions, and run navigator.mediaDevices.enumerateDevices() to obtain persistent device IDs.
  2. Enable third-party-cookie blocking (Chrome’s 3PCD setting).
  3. Navigate to https://publisher.test which triggers a subresource request to https://tracker.test/px.
  4. Configure https://tracker.test/px to respond with Clear-Site-Data: "cookies".
  5. Navigate back to https://tracker.test as a first-party site, call enumerateDevices() again, and check if the device IDs have rotated. (If a first-party tab was open during step 4, check if it received an unexpected devicechange event via base::SystemMonitor).

Suggested Fix

Prevent clearing unpartitioned media device salts during partitioned cookie-clearing operations by wrapping the service call in a !filter_builder->PartitionedCookiesOnly() check in chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc:

if (!filter_builder->PartitionedCookiesOnly()) {
  if (auto* media_device_salt_service =
          MediaDeviceSaltServiceFactory::GetInstance()->GetForBrowserContext(
              profile_)) {
    content::StoragePartition::StorageKeyMatcherFunction storage_key_matcher;
    if (!filter_builder->MatchesAllOriginsAndDomains()) {
      storage_key_matcher = filter_builder->BuildStorageKeyFilter();
    }
    media_device_salt_service->DeleteSalts(
        delete_begin_, delete_end_, std::move(storage_key_matcher),
        CreateTaskCompletionClosure(TracingDataType::kMediaDeviceSalts));
  }
}

Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040


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

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

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker