Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in TabStrip
DescriptionIncorrect authorization in TabStrip
ComponentTabStrip
Bug ClassLogic Error
Tracker522302504
Fix commit6c3c52528bb2 (chromium/src) +128/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-01

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
modified
DetachTabWithDeferredShowDragControllerTest
chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
modified
DetachTabWithDeferredShowDragControllerTest
chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
modified
BindLambdaForTesting
chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
modified

Files Changed

  • chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
  • chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
From 6c3c52528bb2ddd5223595dafb705f7addde535f Mon Sep 17 00:00:00 2001
From: Eriko Kurimoto <elkurin@google.com>
Date: Wed, 29 Jul 2026 21:08:56 -0700
Subject: [PATCH] [Ozone] Suppress data drag during tab-detach visibility wait

When InitialWebUI defers Show() for a freshly-detached browser,
TabDragController::DetachIntoNewBrowserAndRunMoveLoop spins a
kNestableTasksAllowed RunLoop (VisibilityWaiter) before reaching
Widget::RunMoveLoop, and therefore before the
ScopedSuppressForWindowMove() scope established by
DesktopWindowTreeHostPlatform::RunMoveLoop takes effect. A data drag
started from another top-level window inside that loop is not rejected
by the process-global guard and on X11 ends up releasing the tab drag's
pointer capture and running its own XDND move loop on the user's
still-held button.

Extend the suppression scope to also cover the VisibilityWaiter loop on
Linux, mirroring the existing scope around RunMoveLoop.

Add an interactive_ui_test that enables the deferred-Show path, detaches
a tab, and from a task posted into the kWaitingForWindowToShow loop
attempts a data drag on the source window's DragDropClient and expects
it to be rejected with kNone.

Bug: 522302504
Change-Id: I5b50e43f446718f2eab18cbb3bb297442b67bbc8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8140363
Reviewed-by: Kaan Alsan <alsan@chromium.org>
Auto-Submit: Eriko Kurimoto <elkurin@chromium.org>
Commit-Queue: Eriko Kurimoto <elkurin@chromium.org>
Reviewed-by: Elly <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1670819}
---

diff --git a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
index 553fc6af..c4e5d46e 100644
--- a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
@@ -102,6 +102,10 @@
 #include "ui/wm/core/window_modality_controller.h"  // nogncheck
 #endif
 
+#if BUILDFLAG(IS_LINUX)
+#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h"
+#endif
+
 using content::OpenURLParams;
 using content::WebContents;
 
@@ -1767,6 +1771,13 @@
       current_state_ = DragState::kWaitingForWindowToShow;
       VisibilityWaiter waiter(dragged_widget);
 
+#if BUILDFLAG(IS_LINUX)
+      // VisibilityWaiter runs a kNestableTasksAllowed loop while the user holds
+      // the pointer button, before the move loop's own suppression scope is
+      // established; suppress data drags for its duration as well.
+      auto suppress_data_drag =
+          views::DesktopDragDropClientOzone::ScopedSuppressForWindowMove();
+#endif
       base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
       waiter.Wait();
       if (!ref) {
diff --git a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
index 76662db..0efae4b 100644
--- a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
@@ -159,6 +159,9 @@
 
 #if BUILDFLAG(IS_LINUX)
 #include "chrome/browser/ui/views/frame/browser_native_widget_aura_linux.h"
+#include "chrome/common/chrome_features.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/linux/linux_ui.h"
 #define DESKTOP_BROWSER_FRAME_AURA BrowserNativeWidgetAuraLinux
 #else
@@ -688,6 +691,14 @@
          GetTabDragController(tab_strip)->started_drag();
 }
 
+bool TabDragControllerTest::IsWaitingForWindowToShow(
+    TabStrip* tab_strip) const {
+  TabDragController* controller = GetTabDragController(tab_strip);
+  return controller &&
+         controller->current_state_ ==
+             TabDragController::DragState::kWaitingForWindowToShow;
+}
+
 void TabDragControllerTest::SetTabDragPointResolver(
     TabDragPointResolver& resolver) {
   TabDragController::SetTabDragPointResolver(resolver);
@@ -3652,6 +3663,106 @@
 
 #endif  // BUILDFLAG(IS_MAC)
 
+#if BUILDFLAG(IS_LINUX)
+
+// Variant of DetachToBrowserTabDragControllerTest that enables InitialWebUI
+// with deferred BrowserView::Show() so that the detached browser is not
+// immediately visible after Show() and TabDragController spins its
+// kWaitingForWindowToShow nested RunLoop before entering the move loop.
+class DetachTabWithDeferredShowDragControllerTest
+    : public DetachToBrowserTabDragControllerTest {
+ public:
+  DetachTabWithDeferredShowDragControllerTest() {
+    deferred_show_feature_list_.InitWithFeaturesAndParameters(
+        {{features::kInitialWebUI, {}},
+         {features::kWebUIReloadButton, {}},
+         {features::kArtificialUIDelay,
+          {{"initial_web_ui_delay_duration", "2s"},
+           {"views_ui_delay_duration", "0ms"}}}},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList deferred_show_feature_list_;
+};
+
+// Detaches a tab into a new browser and, while the controller is waiting for
+// the new browser's deferred Show() to complete, attempts to start a data drag
+// from the source browser's window. The data drag must be rejected so that it
+// cannot release the tab drag's pointer capture and take over the gesture.
+IN_PROC_BROWSER_TEST_P(DetachTabWithDeferredShowDragControllerTest,
+                       RejectDataDragWhileWaitingForDetachedWindow) {
+  // The kWaitingForWindowToShow state is only reached on the RunMoveLoop path.
+  if (!test::PlatformSupportsScreenCoordinates()) {
+    GTEST_SKIP() << "RunMoveLoop is not used on this platform.";
+  }
+
+  AddTabsAndResetBrowser(browser(), 1);
+  TabStrip* tab_strip = GetTabStripForBrowser(browser());
+
+  aura::Window* const source_window = browser()->GetWindow()->GetNativeWindow();
+  aura::Window* const source_root = source_window->GetRootWindow();
+
+  bool data_drag_attempted = false;
+  TabStrip* new_tab_strip = nullptr;
+  // Releases the pointer once the controller has left kWaitingForWindowToShow
+  // and entered the move loop, so the move loop sees the button-up.
+  base::RepeatingClosure release_when_in_move_loop =
+      base::BindLambdaForTesting([&]() {
+        if (new_tab_strip && IsWaitingForWindowToShow(new_tab_strip)) {
+          base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+              FROM_HERE, release_when_in_move_loop);
+          return;
+        }
+        ASSERT_TRUE(ReleaseInput(0, /*async=*/true));
+      });
+
+  Tab* const tab = tab_strip->tab_at(0);
+  ASSERT_TRUE(PressInputAtCenter(tab));
+  ASSERT_TRUE(DragInputToCenterNotifyWhenDone(
+      tab, base::BindLambdaForTesting([&]() {
+        // The mouse-move that triggered detach has entered the
+        // kWaitingForWindowToShow nested loop; post into it.
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindLambdaForTesting([&]() {
+              data_drag_attempted = true;
+              ASSERT_TRUE(TabDragController::IsActive());
+              ASSERT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());
+              BrowserWindowInterface* const new_browser =
+                  ui_test_utils::GetBrowserNotInSet({browser()});
+              ASSERT_TRUE(new_browser);
+              new_tab_strip = GetTabStripForBrowser(new_browser);
+              ASSERT_TRUE(IsWaitingForWindowToShow(new_tab_strip));
+
+              auto data = std::make_unique<ui::OSExchangeData>();
+              data->SetString(u"test");
+              ui::mojom::DragOperation result =
+                  aura::client::GetDragDropClient(source_root)
+                      ->StartDragAndDrop(std::move(data), source_root,
+                                         source_window, gfx::Point(),
+                                         ui::DragDropTypes::DRAG_COPY,
+                                         ui::mojom::DragEventSource::kMouse);
+              EXPECT_EQ(ui::mojom::DragOperation::kNone, result);
+
+              release_when_in_move_loop.Run();
+            }));
+      }),
+      gfx::Vector2d(0, GetDetachY(tab_strip))));
+
+  ASSERT_TRUE(
+      base::test::RunUntil([]() { return !TabDragController::IsActive(); }));
+  EXPECT_TRUE(data_drag_attempted);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TabDragging,
+    DetachTabWithDeferredShowDragControllerTest,
+    ::testing::Combine(
+        /*kTearOffWebAppTabOpensWebAppWindow=*/::testing::Values(false),
+        /*input_source=*/::testing::Values("mouse")));
+
+#endif  // BUILDFLAG(IS_LINUX)
+
 namespace {
 
 // Invoked from the nested run loop.
diff --git a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.h b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.h
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
index 76662db..0efae4b 100644
--- a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.cc
@@ -159,6 +159,9 @@
 
 #if BUILDFLAG(IS_LINUX)
 #include "chrome/browser/ui/views/frame/browser_native_widget_aura_linux.h"
+#include "chrome/common/chrome_features.h"
+#include "ui/base/dragdrop/drag_drop_types.h"
+#include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/linux/linux_ui.h"
 #define DESKTOP_BROWSER_FRAME_AURA BrowserNativeWidgetAuraLinux
 #else
@@ -688,6 +691,14 @@
          GetTabDragController(tab_strip)->started_drag();
 }
 
+bool TabDragControllerTest::IsWaitingForWindowToShow(
+    TabStrip* tab_strip) const {
+  TabDragController* controller = GetTabDragController(tab_strip);
+  return controller &&
+         controller->current_state_ ==
+             TabDragController::DragState::kWaitingForWindowToShow;
+}
+
 void TabDragControllerTest::SetTabDragPointResolver(
     TabDragPointResolver& resolver) {
   TabDragController::SetTabDragPointResolver(resolver);
@@ -3652,6 +3663,106 @@
 
 #endif  // BUILDFLAG(IS_MAC)
 
+#if BUILDFLAG(IS_LINUX)
+
+// Variant of DetachToBrowserTabDragControllerTest that enables InitialWebUI
+// with deferred BrowserView::Show() so that the detached browser is not
+// immediately visible after Show() and TabDragController spins its
+// kWaitingForWindowToShow nested RunLoop before entering the move loop.
+class DetachTabWithDeferredShowDragControllerTest
+    : public DetachToBrowserTabDragControllerTest {
+ public:
+  DetachTabWithDeferredShowDragControllerTest() {
+    deferred_show_feature_list_.InitWithFeaturesAndParameters(
+        {{features::kInitialWebUI, {}},
+         {features::kWebUIReloadButton, {}},
+         {features::kArtificialUIDelay,
+          {{"initial_web_ui_delay_duration", "2s"},
+           {"views_ui_delay_duration", "0ms"}}}},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList deferred_show_feature_list_;
+};
+
+// Detaches a tab into a new browser and, while the controller is waiting for
+// the new browser's deferred Show() to complete, attempts to start a data drag
+// from the source browser's window. The data drag must be rejected so that it
+// cannot release the tab drag's pointer capture and take over the gesture.
+IN_PROC_BROWSER_TEST_P(DetachTabWithDeferredShowDragControllerTest,
+                       RejectDataDragWhileWaitingForDetachedWindow) {
+  // The kWaitingForWindowToShow state is only reached on the RunMoveLoop path.
+  if (!test::PlatformSupportsScreenCoordinates()) {
+    GTEST_SKIP() << "RunMoveLoop is not used on this platform.";
+  }
+
+  AddTabsAndResetBrowser(browser(), 1);
+  TabStrip* tab_strip = GetTabStripForBrowser(browser());
+
+  aura::Window* const source_window = browser()->GetWindow()->GetNativeWindow();
+  aura::Window* const source_root = source_window->GetRootWindow();
+
+  bool data_drag_attempted = false;
+  TabStrip* new_tab_strip = nullptr;
+  // Releases the pointer once the controller has left kWaitingForWindowToShow
+  // and entered the move loop, so the move loop sees the button-up.
+  base::RepeatingClosure release_when_in_move_loop =
+      base::BindLambdaForTesting([&]() {
+        if (new_tab_strip && IsWaitingForWindowToShow(new_tab_strip)) {
+          base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+              FROM_HERE, release_when_in_move_loop);
+          return;
+        }
+        ASSERT_TRUE(ReleaseInput(0, /*async=*/true));
+      });
+
+  Tab* const tab = tab_strip->tab_at(0);
+  ASSERT_TRUE(PressInputAtCenter(tab));
+  ASSERT_TRUE(DragInputToCenterNotifyWhenDone(
+      tab, base::BindLambdaForTesting([&]() {
+        // The mouse-move that triggered detach has entered the
+        // kWaitingForWindowToShow nested loop; post into it.
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindLambdaForTesting([&]() {
+              data_drag_attempted = true;
+              ASSERT_TRUE(TabDragController::IsActive());
+              ASSERT_EQ(2u, GlobalBrowserCollection::GetInstance()->GetSize());
+              BrowserWindowInterface* const new_browser =
+                  ui_test_utils::GetBrowserNotInSet({browser()});
+              ASSERT_TRUE(new_browser);
+              new_tab_strip = GetTabStripForBrowser(new_browser);
+              ASSERT_TRUE(IsWaitingForWindowToShow(new_tab_strip));
+
+              auto data = std::make_unique<ui::OSExchangeData>();
+              data->SetString(u"test");
+              ui::mojom::DragOperation result =
+                  aura::client::GetDragDropClient(source_root)
+                      ->StartDragAndDrop(std::move(data), source_root,
+                                         source_window, gfx::Point(),
+                                         ui::DragDropTypes::DRAG_COPY,
+                                         ui::mojom::DragEventSource::kMouse);
+              EXPECT_EQ(ui::mojom::DragOperation::kNone, result);
+
+              release_when_in_move_loop.Run();
+            }));
+      }),
+      gfx::Vector2d(0, GetDetachY(tab_strip))));
+
+  ASSERT_TRUE(
+      base::test::RunUntil([]() { return !TabDragController::IsActive(); }));
+  EXPECT_TRUE(data_drag_attempted);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TabDragging,
+    DetachTabWithDeferredShowDragControllerTest,
+    ::testing::Combine(
+        /*kTearOffWebAppTabOpensWebAppWindow=*/::testing::Values(false),
+        /*input_source=*/::testing::Values("mouse")));
+
+#endif  // BUILDFLAG(IS_LINUX)
+
 namespace {
 
 // Invoked from the nested run loop.
diff --git a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.h b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.h
index 5b50d9c..afdd71b 100644
--- a/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.h
+++ b/chrome/browser/ui/views/tabs/dragging/tab_drag_controller_interactive_uitest.h
@@ -62,6 +62,11 @@
 
   bool HasDragStarted(TabStrip* tab_strip) const;
 
+  // Returns true if `tab_strip`'s drag controller is in the state where it has
+  // detached into a new browser and is waiting for that browser's widget to
+  // become visible before entering the window-move loop.
+  bool IsWaitingForWindowToShow(TabStrip* tab_strip) const;
+
   void SetTabDragPointResolver(TabDragPointResolver& resolver);
 
   // InProcessBrowserTest:
Loading diff…

Original Bug Report

reported by vm...@google.com

Gesture hijack in TabDragController via nested VisibilityWaiter run loop

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: A potential vulnerability in TabDragController allows a compromised renderer to hijack an active tab-drag gesture. When a tab is detached into a new window under the kInitialWebUI feature, a nested RunLoop spins before global drag-suppression guards are set. An attacker can potentially exploit this window of opportunity to release the user’s active mouse grab and substitute an attacker-controlled drag-and-drop payload.

Affected files:

  • chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
  • ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
  • ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
  • ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc

Estimated timestamp from git blame: 2026-04-01

Summary

We have identified a potential gesture-hijack vulnerability in TabDragController where a compromised renderer can potentially intercept and hijack an active user tab-drag gesture.

When a user drags a tab to detach it into a new window, if the kInitialWebUI feature is enabled (which is currently active in the WebUIReloadButtonStudy field trial on Linux, macOS, and Windows), the browser process temporarily spins a nested base::RunLoop of type kNestableTasksAllowed inside VisibilityWaiter::Wait() while awaiting the first paint. Because this nested loop runs before the platform’s native window-movement loop begins, the global re-entrancy prevention guard g_is_dragging has not yet been set. A compromised renderer can potentially exploit this timing window to request a new drag-and-drop session, hijacking the user’s active pointer grab or triggering a browser crash.

Note: Since our security evaluation tooling does not have the ability to execute code, these are potential steps and analysis based on first-principles inspection of the source code.

Technical Analysis

When a tab is detached, TabDragController::DetachIntoNewBrowserAndRunMoveLoop is invoked:

// chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
if (base::FeatureList::IsEnabled(features::kInitialWebUI)) {
  if (!dragged_widget->IsVisible()) {
    current_state_ = DragState::kWaitingForWindowToShow;
    VisibilityWaiter waiter(dragged_widget);
    base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
    waiter.Wait();                              // Pumps Mojo IPCs via nestable run loop
    if (!ref) { return Liveness::kDeleted; }
    current_state_ = DragState::kDraggingWindow;
  }
}

Inside VisibilityWaiter, a nested run loop is started:

class VisibilityWaiter : public views::WidgetObserver {
  ...
  void Wait() { run_loop_.Run(); }
  base::RunLoop run_loop_{base::RunLoop::Type::kNestableTasksAllowed};
};

Because the nested loop permits nested tasks, the UI thread processes incoming Mojo messages.

Normally, when the browser enters a window-move loop, DesktopWindowTreeHostPlatform::RunMoveLoop instantiates ScopedSuppressForWindowMove() to set g_is_dragging = true globally, preventing renderer-initiated drag-and-drop:

// ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
Widget::MoveLoopResult DesktopWindowTreeHostPlatform::RunMoveLoop(...) {
  auto suppress_data_drag =
      DesktopDragDropClientOzone::ScopedSuppressForWindowMove();
  ...
}

However, because VisibilityWaiter::Wait() spins before RunMoveLoop is reached, g_is_dragging remains false.

Exploit Mechanism

  1. A compromised renderer in another visible window (e.g., W2) issues a LocalFrameHost::StartDragging Mojo IPC.
  2. The browser UI thread dispatches the IPC while blocked inside waiter.Wait(), leading to WebContentsViewAura::StartDragging (in content/browser/web_contents/web_contents_view_aura.cc).
  3. Since the attacker-controlled window W2 does not have an active webpage-initiated drag session, drag_security_info_.did_initiate() returns false, bypassing the WebContents re-entrancy check.
  4. The request reaches DesktopDragDropClientOzone::StartDragAndDrop (or DesktopDragDropClientWin::StartDragAndDrop). Since g_is_dragging is false, the re-entrancy checks pass.
  5. On Ozone/X11 or Windows:
    • The platform client releases the user’s active tab-drag pointer capture (capture_window->ReleaseCapture()) and initiates a new system drag session, hijacking the physical pointer grab.
    • When the user releases the mouse button, the attacker’s custom drag-and-drop payload (such as a javascript: URL) is dropped onto the drop target.
  6. On Ozone/Wayland:
    • Registering a new drag delegate while a drag session is active triggers a browser crash due to CHECK(!drag_delegate_) in ui/ozone/platform/wayland/host/wayland_data_device.cc:48.

Potential Steps to Trigger

  1. Ensure the kInitialWebUI feature is active (e.g., by launching with --enable-features=InitialWebUI).
  2. Compromise a renderer process and open a second top-level visible window W2.
  3. Have the user initiate a tab-detach gesture.
  4. While the tab-drag widget is waiting to show itself (inside VisibilityWaiter::Wait()), the compromised renderer sends a LocalFrameHost::StartDragging IPC message.
  5. The browser processes the drag request, releasing pointer capture from the tab-drag widget and binding the active pointer grab to the attacker’s new drag-and-drop session or triggering a crash.

Suggested Fix

To mitigate this potential issue, we should prevent renderer-initiated drags from being processed while TabDragController is in a waiting state, or instantiate the suppression scoper earlier.

One potential fix is to instantiate ScopedSuppressForWindowMove inside TabDragController::DetachIntoNewBrowserAndRunMoveLoop prior to executing waiter.Wait() to suppress incoming drag-and-drop requests during the paint delay window.

Evaluated with Chrome root at commit: b2fea2e31df308d0f04e4ae47def4c4f939ee141


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