[OTP][PhishGuard] Do phish guard checks before showing OTPs.

Bug: 415273169
Change-Id: If02fabf80a9cfcf6542856dd72670ccec931ba5c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7093137
Reviewed-by: Matthias Körber <koerber@google.com>
Reviewed-by: Jan Keitel <jkeitel@google.com>
Commit-Queue: Luchen Peng <luchenpeng@google.com>
Cr-Commit-Position: refs/heads/main@{#1537847}
diff --git a/chrome/browser/ui/autofill/BUILD.gn b/chrome/browser/ui/autofill/BUILD.gn
index 73dd223..67a7c6e 100644
--- a/chrome/browser/ui/autofill/BUILD.gn
+++ b/chrome/browser/ui/autofill/BUILD.gn
@@ -127,6 +127,8 @@
     "autofill_suggestion_controller.cc",
     "autofill_suggestion_controller_utils.cc",
     "chrome_autofill_client.cc",
+    "chrome_otp_phish_guard_delegate.cc",
+    "chrome_otp_phish_guard_delegate.h",
     "next_idle_barrier.cc",
     "popup_controller_common.cc",
     "risk_util.cc",
@@ -169,6 +171,7 @@
     "//components/password_manager/core/browser:password_form",
     "//components/password_manager/core/browser/form_parsing",
     "//components/profile_metrics",
+    "//components/safe_browsing/content/browser/password_protection",
     "//components/security_state/content",
     "//components/strings:components_strings_grit",
     "//components/unified_consent",
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 0acea8c..0873d82 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -57,6 +57,7 @@
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/ui/autofill/address_bubbles_controller.h"
 #include "chrome/browser/ui/autofill/autofill_suggestion_controller.h"
+#include "chrome/browser/ui/autofill/chrome_otp_phish_guard_delegate.h"
 #include "chrome/browser/ui/autofill/edit_address_profile_dialog_controller_impl.h"
 #include "chrome/browser/ui/autofill/payments/chrome_payments_autofill_client.h"
 #include "chrome/browser/ui/autofill/payments/credit_card_scanner_controller.h"
@@ -1261,7 +1262,9 @@
       ablation_study_(g_browser_process->local_state()),
       identity_credential_delegate_(web_contents),
       otp_field_detector_(std::make_unique<OtpFieldDetector>(this)),
-      email_verifier_delegate_(std::make_unique<EmailVerifierDelegate>(this)) {
+      email_verifier_delegate_(std::make_unique<EmailVerifierDelegate>(this)),
+      otp_phish_guard_delegate_(
+          std::make_unique<ChromeOtpPhishGuardDelegate>(web_contents)) {
   // Initialize StrikeDatabase so its cache will be loaded and ready to use
   // when requested by other Autofill classes.
   GetStrikeDatabase();
@@ -1352,6 +1355,16 @@
   return otp_field_detector_.get();
 }
 
+OtpPhishGuardDelegate* ChromeAutofillClient::GetOtpPhishGuardDelegate() {
+#if BUILDFLAG(IS_ANDROID)
+  if (base::FeatureList::IsEnabled(
+          password_manager::features::kOtpPhishGuard)) {
+    return otp_phish_guard_delegate_.get();
+  }
+#endif  // BUILDFLAG(IS_ANDROID)
+  return nullptr;
+}
+
 one_time_tokens::OneTimeTokenService*
 ChromeAutofillClient::GetOneTimeTokenService() const {
 #if BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index dcca7871..23ca31dd 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -66,6 +66,7 @@
 class EmailVerifierDelegate;
 class FormFieldData;
 class OtpFieldDetector;
+class ChromeOtpPhishGuardDelegate;
 class LogRouter;
 enum class SuggestionType;
 
@@ -281,6 +282,7 @@
       override;
 
   OtpFieldDetector* GetOtpFieldDetector() override;
+  OtpPhishGuardDelegate* GetOtpPhishGuardDelegate() override;
 
   one_time_tokens::OneTimeTokenService* GetOneTimeTokenService() const final;
 
@@ -343,6 +345,7 @@
   ContentIdentityCredentialDelegate identity_credential_delegate_;
   std::unique_ptr<OtpFieldDetector> otp_field_detector_;
   std::unique_ptr<EmailVerifierDelegate> email_verifier_delegate_;
+  std::unique_ptr<ChromeOtpPhishGuardDelegate> otp_phish_guard_delegate_;
 
   base::WeakPtrFactory<ChromeAutofillClient> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/ui/autofill/chrome_otp_phish_guard_delegate.cc b/chrome/browser/ui/autofill/chrome_otp_phish_guard_delegate.cc
new file mode 100644
index 0000000..78290f0
--- /dev/null
+++ b/chrome/browser/ui/autofill/chrome_otp_phish_guard_delegate.cc
@@ -0,0 +1,35 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/autofill/chrome_otp_phish_guard_delegate.h"
+
+#include "chrome/browser/password_manager/chrome_password_manager_client.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/safe_browsing/content/browser/password_protection/password_protection_service.h"
+#include "content/public/browser/web_contents.h"
+
+namespace autofill {
+
+ChromeOtpPhishGuardDelegate::ChromeOtpPhishGuardDelegate(
+    content::WebContents* web_contents)
+    : web_contents_(CHECK_DEREF(web_contents)) {}
+
+ChromeOtpPhishGuardDelegate::~ChromeOtpPhishGuardDelegate() = default;
+
+void ChromeOtpPhishGuardDelegate::StartOtpPhishGuardCheck(
+    const GURL& url,
+    base::OnceCallback<void(bool)> callback) {
+  if (auto* client =
+          ChromePasswordManagerClient::FromWebContents(&web_contents_.get())) {
+    if (safe_browsing::PasswordProtectionService* pps =
+            client->GetPasswordProtectionService()) {
+      pps->MaybeStartOtpPhishingRequest(&web_contents_.get(), url,
+                                        std::move(callback));
+      return;
+    }
+  }
+  std::move(callback).Run(false);
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/autofill/chrome_otp_phish_guard_delegate.h b/chrome/browser/ui/autofill/chrome_otp_phish_guard_delegate.h
new file mode 100644
index 0000000..e36998a
--- /dev/null
+++ b/chrome/browser/ui/autofill/chrome_otp_phish_guard_delegate.h
@@ -0,0 +1,34 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_CHROME_OTP_PHISH_GUARD_DELEGATE_H_
+#define CHROME_BROWSER_UI_AUTOFILL_CHROME_OTP_PHISH_GUARD_DELEGATE_H_
+
+#include "base/check_deref.h"
+#include "base/memory/raw_ref.h"
+#include "components/autofill/core/browser/integrators/one_time_tokens/otp_phish_guard_delegate.h"
+
+namespace content {
+class WebContents;
+}
+
+namespace autofill {
+
+class ChromeOtpPhishGuardDelegate : public OtpPhishGuardDelegate {
+ public:
+  explicit ChromeOtpPhishGuardDelegate(content::WebContents* web_contents);
+  ~ChromeOtpPhishGuardDelegate() override;
+
+  // OtpPhishGuardDelegate:
+  void StartOtpPhishGuardCheck(
+      const GURL& url,
+      base::OnceCallback<void(bool)> callback) override;
+
+ private:
+  const raw_ref<content::WebContents> web_contents_;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_CHROME_OTP_PHISH_GUARD_DELEGATE_H_
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 9c49647..cfaa826a 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -414,6 +414,7 @@
     "integrators/one_time_tokens/otp_manager.h",
     "integrators/one_time_tokens/otp_manager_impl.cc",
     "integrators/one_time_tokens/otp_manager_impl.h",
+    "integrators/one_time_tokens/otp_phish_guard_delegate.h",
     "integrators/one_time_tokens/otp_suggestion.cc",
     "integrators/one_time_tokens/otp_suggestion.h",
     "integrators/optimization_guide/autofill_optimization_guide_decider.cc",
diff --git a/components/autofill/core/browser/foundations/autofill_client.cc b/components/autofill/core/browser/foundations/autofill_client.cc
index 70ea6bd4..27ef79a 100644
--- a/components/autofill/core/browser/foundations/autofill_client.cc
+++ b/components/autofill/core/browser/foundations/autofill_client.cc
@@ -339,4 +339,8 @@
   return nullptr;
 }
 
+OtpPhishGuardDelegate* AutofillClient::GetOtpPhishGuardDelegate() {
+  return nullptr;
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/foundations/autofill_client.h b/components/autofill/core/browser/foundations/autofill_client.h
index 3dfcb5d..e70e75c1 100644
--- a/components/autofill/core/browser/foundations/autofill_client.h
+++ b/components/autofill/core/browser/foundations/autofill_client.h
@@ -115,6 +115,7 @@
 struct FormInteractionsFlowId;
 class LogManager;
 class OtpFieldDetector;
+class OtpPhishGuardDelegate;
 struct PasswordFormClassification;
 class PasswordManagerDelegate;
 class PersonalDataManager;
@@ -751,6 +752,10 @@
   // May return null on platforms where OTPs are not supported.
   virtual OtpFieldDetector* GetOtpFieldDetector();
 
+  // Returns the delegate for OTP phish guard, which can be used to perform
+  // security checks before offering an OTP. May return nullptr.
+  virtual OtpPhishGuardDelegate* GetOtpPhishGuardDelegate();
+
   // May return null on platforms where no OneTimeTokenService is supported.
   virtual one_time_tokens::OneTimeTokenService* GetOneTimeTokenService() const;
 
diff --git a/components/autofill/core/browser/foundations/test_autofill_client.h b/components/autofill/core/browser/foundations/test_autofill_client.h
index 4052e98..603baf4 100644
--- a/components/autofill/core/browser/foundations/test_autofill_client.h
+++ b/components/autofill/core/browser/foundations/test_autofill_client.h
@@ -38,6 +38,7 @@
 #include "components/autofill/core/browser/integrators/autofill_ai/mock_autofill_ai_manager.h"
 #include "components/autofill/core/browser/integrators/fast_checkout/mock_fast_checkout_client.h"
 #include "components/autofill/core/browser/integrators/identity_credential/identity_credential_delegate.h"
+#include "components/autofill/core/browser/integrators/one_time_tokens/otp_phish_guard_delegate.h"
 #include "components/autofill/core/browser/integrators/optimization_guide/mock_autofill_optimization_guide_decider.h"
 #include "components/autofill/core/browser/integrators/password_manager/password_manager_delegate.h"
 #include "components/autofill/core/browser/integrators/plus_addresses/autofill_plus_address_delegate.h"
@@ -457,6 +458,15 @@
   bool IsLastQueriedField(FieldGlobalId field_id) override { return true; }
 #endif
 
+  OtpPhishGuardDelegate* GetOtpPhishGuardDelegate() override {
+    return otp_phish_guard_delegate_.get();
+  }
+
+  void set_otp_phish_guard_delegate(
+      std::unique_ptr<OtpPhishGuardDelegate> otp_phish_guard_delegate) {
+    otp_phish_guard_delegate_ = std::move(otp_phish_guard_delegate);
+  }
+
   void set_test_addresses(
       std::vector<AutofillProfile> test_addresses) override {
     for (AutofillProfile& profile : test_addresses) {
@@ -681,6 +691,7 @@
   ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
   signin::IdentityTestEnvironment identity_test_env_;
   raw_ptr<syncer::SyncService> test_sync_service_ = nullptr;
+  std::unique_ptr<OtpPhishGuardDelegate> otp_phish_guard_delegate_;
   std::unique_ptr<AutofillPlusAddressDelegate> plus_address_delegate_;
   std::unique_ptr<IdentityCredentialDelegate> identity_credential_delegate_;
   std::unique_ptr<PasswordManagerDelegate> password_manager_delegate_;
diff --git a/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl.cc b/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl.cc
index 0411071..9936c5b 100644
--- a/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl.cc
+++ b/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl.cc
@@ -10,6 +10,7 @@
 #include "components/autofill/core/browser/autofill_field.h"
 #include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/foundations/browser_autofill_manager.h"
+#include "components/autofill/core/browser/integrators/one_time_tokens/otp_phish_guard_delegate.h"
 #include "components/password_manager/core/browser/features/password_features.h"
 
 using one_time_tokens::ExpiringSubscriptionHandle;
@@ -100,6 +101,7 @@
     std::variant<OneTimeToken, OneTimeTokenRetrievalError> token_or_error) {
   // TODO(crbug.com/415272524): Record metrics on how often the retrieval
   // succeeds or fails, in combination with the OTP source.
+  // If token_or_error holds an error, run the callback with empty otp value.
   if (std::holds_alternative<OneTimeTokenRetrievalError>(token_or_error)) {
     if (!last_pending_get_suggestions_callback_.is_null()) {
       std::move(last_pending_get_suggestions_callback_).Run({});
@@ -107,23 +109,46 @@
     return;
   }
 
-  const OneTimeToken& token = std::get<OneTimeToken>(token_or_error);
+  // If we are here, token_or_error holds a OneTimeToken, we check if the
+  // callback is valid.
+  if (!last_pending_get_suggestions_callback_) {
+    return;
+  }
+
+  OneTimeToken& token = std::get<OneTimeToken>(token_or_error);
+
+  // We run PhishGuard check to make sure OTPs are not shown to users on
+  // potential phishing sites.
+  if (OtpPhishGuardDelegate* delegate =
+          owner_->client().GetOtpPhishGuardDelegate()) {
+    delegate->StartOtpPhishGuardCheck(
+        owner_->client().GetLastCommittedPrimaryMainFrameURL(),
+        base::BindOnce(&OtpManagerImpl::MaybeShowOtpSuggestions,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(token)));
+  } else {
+    MaybeShowOtpSuggestions(std::move(token), /*is_phishing_site=*/false);
+  }
+}
+
+void OtpManagerImpl::MaybeShowOtpSuggestions(OneTimeToken token,
+                                             bool is_phishing_site) {
+  if (!last_pending_get_suggestions_callback_) {
+    return;
+  }
 
   std::vector<std::string> suggestions;
   if (!token.value().empty()) {
-    suggestions.emplace_back(token.value());
+    suggestions.emplace_back(std::move(token).value());
   }
 
-  if (IsOtpDeliveryBlocked()) {
+  if (IsOtpDeliveryBlocked() || is_phishing_site) {
     suggestions.clear();
   }
 
-  if (!last_pending_get_suggestions_callback_.is_null()) {
-    if (owner_->GetMetricState().has_value()) {
-      owner_->GetMetricState()->otp_form_event_logger.OnOtpAvailable();
-    }
-    std::move(last_pending_get_suggestions_callback_).Run(suggestions);
+  if (owner_->GetMetricState().has_value()) {
+    owner_->GetMetricState()->otp_form_event_logger.OnOtpAvailable();
   }
+  std::move(last_pending_get_suggestions_callback_).Run(std::move(suggestions));
 }
 
 bool OtpManagerImpl::IsOtpDeliveryBlocked() {
diff --git a/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl.h b/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl.h
index f966eb7f..9a36064 100644
--- a/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl.h
+++ b/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl.h
@@ -61,6 +61,10 @@
       std::variant<one_time_tokens::OneTimeToken,
                    one_time_tokens::OneTimeTokenRetrievalError>);
 
+  // Callback for `OtpPhishGuardDelegate::StartOtpPhishGuardCheck`.
+  void MaybeShowOtpSuggestions(one_time_tokens::OneTimeToken token,
+                               bool is_phishing_site);
+
   // Returns true if an OTP must not be delivered to the caller in an autofill
   // context, e.g., because the page called the WebOTP API.
   bool IsOtpDeliveryBlocked();
@@ -80,6 +84,10 @@
   // call to `GetOtpSuggestions()` invalidates the previous call.
   GetOtpSuggestionsCallback last_pending_get_suggestions_callback_;
 
+  // The last received OTP. This is used to store the OTP between the phishing
+  // check and the actual display of the suggestions.
+  std::optional<one_time_tokens::OneTimeToken> last_received_otp_;
+
   base::ScopedObservation<BrowserAutofillManager, AutofillManager::Observer>
       autofill_manager_observation_{this};
 
diff --git a/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl_unittest.cc b/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl_unittest.cc
index 3eb2c79..163feba 100644
--- a/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl_unittest.cc
+++ b/components/autofill/core/browser/integrators/one_time_tokens/otp_manager_impl_unittest.cc
@@ -27,7 +27,7 @@
 using autofill::test::GetServerTypes;
 using base::test::RunOnceCallback;
 using one_time_tokens::OneTimeTokenServiceImpl;
-
+using ::testing::_;
 namespace autofill {
 
 namespace {
@@ -40,6 +40,14 @@
               (override));
 };
 
+class MockOtpPhishGuardDelegate : public OtpPhishGuardDelegate {
+ public:
+  MOCK_METHOD(void,
+              StartOtpPhishGuardCheck,
+              (const GURL&, base::OnceCallback<void(bool)>),
+              (override));
+};
+
 }  // namespace
 
 class OtpManagerImplTest : public testing::Test,
@@ -50,6 +58,10 @@
 
   void SetUp() override {
     InitAutofillClient();
+    auto otp_phish_guard_delegate =
+        std::make_unique<MockOtpPhishGuardDelegate>();
+    autofill_client().set_otp_phish_guard_delegate(
+        std::move(otp_phish_guard_delegate));
     CreateAutofillDriver();
   }
 
@@ -95,6 +107,11 @@
     AddForm(form_description);
   }
 
+  MockOtpPhishGuardDelegate& otp_phish_guard_delegate() {
+    return static_cast<MockOtpPhishGuardDelegate&>(
+        *autofill_client().GetOtpPhishGuardDelegate());
+  }
+
  protected:
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
@@ -132,7 +149,9 @@
   // Prepare the handling of SMS requests from the SMS backend.
   one_time_tokens::OtpFetchReply reply = GetDefaultOtpFetchReply();
   EXPECT_CALL(sms_otp_backend_, RetrieveSmsOtp)
-      .WillOnce(RunOnceCallback<0>(GetDefaultOtpFetchReply()));
+      .WillOnce(RunOnceCallback<0>(reply));
+  EXPECT_CALL(otp_phish_guard_delegate(), StartOtpPhishGuardCheck)
+      .WillOnce(RunOnceCallback<1>(false));
 
   // Observing an OTP field is supposed to trigger an SMS OTP request.
   AddFormWithOtpField();
@@ -157,6 +176,8 @@
       .WillOnce(
           [&](base::OnceCallback<void(const one_time_tokens::OtpFetchReply&)>
                   callback) { sms_backend_callback = std::move(callback); });
+  EXPECT_CALL(otp_phish_guard_delegate(), StartOtpPhishGuardCheck)
+      .WillOnce(RunOnceCallback<1>(false));
 
   // Observing an OTP field is supposed to trigger an SMS OTP request.
   AddFormWithOtpField();
@@ -181,10 +202,12 @@
 TEST_F(OtpManagerImplTest, GetOtpSuggestions_FetchesSmsOnlyOnce) {
   OtpManagerImpl otp_manager(autofill_manager(), &one_time_token_service_);
 
-  // Prepare the handling of SMS requests from the SMS backend.
   one_time_tokens::OtpFetchReply reply = GetDefaultOtpFetchReply();
   EXPECT_CALL(sms_otp_backend_, RetrieveSmsOtp)
       .WillOnce(RunOnceCallback<0>(GetDefaultOtpFetchReply()));
+  EXPECT_CALL(otp_phish_guard_delegate(), StartOtpPhishGuardCheck)
+      .WillOnce(RunOnceCallback<1>(false))
+      .WillOnce(RunOnceCallback<1>(false));
 
   // Observing an OTP field is supposed to trigger an SMS OTP request.
   AddFormWithOtpField();
@@ -220,6 +243,8 @@
       .WillOnce(
           [&](base::OnceCallback<void(const one_time_tokens::OtpFetchReply&)>
                   callback) { sms_backend_callback = std::move(callback); });
+  EXPECT_CALL(otp_phish_guard_delegate(), StartOtpPhishGuardCheck)
+      .WillOnce(RunOnceCallback<1>(false));
 
   // Observing an OTP field is supposed to trigger an SMS OTP request.
   AddFormWithOtpField();
@@ -262,6 +287,8 @@
 
   EXPECT_CALL(sms_otp_backend_, RetrieveSmsOtp)
       .WillOnce(RunOnceCallback<0>(reply));
+  EXPECT_CALL(otp_phish_guard_delegate(), StartOtpPhishGuardCheck)
+      .WillOnce(RunOnceCallback<1>(false));
 
   // Observing an OTP field is supposed to trigger an SMS OTP request.
   AddFormWithOtpField();
@@ -288,6 +315,8 @@
       .WillOnce(
           [&](base::OnceCallback<void(const one_time_tokens::OtpFetchReply&)>
                   callback) { sms_backend_callback = std::move(callback); });
+  EXPECT_CALL(otp_phish_guard_delegate(), StartOtpPhishGuardCheck)
+      .WillOnce(RunOnceCallback<1>(false));
 
   // Observing an OTP field is supposed to trigger an SMS OTP request.
   AddFormWithOtpField();
@@ -314,4 +343,45 @@
   EXPECT_FALSE(future2.IsReady());
 }
 
+// Tests that no suggestions are returned if the phishing check returns true.
+TEST_F(OtpManagerImplTest, GetOtpSuggestions_PhishingCheckReturnsTrue) {
+  OtpManagerImpl otp_manager(autofill_manager(), &one_time_token_service_);
+
+  // Prepare the handling of SMS requests from the SMS backend.
+  one_time_tokens::OtpFetchReply reply = GetDefaultOtpFetchReply();
+  EXPECT_CALL(sms_otp_backend_, RetrieveSmsOtp)
+      .WillOnce(RunOnceCallback<0>(reply));
+  EXPECT_CALL(otp_phish_guard_delegate(), StartOtpPhishGuardCheck)
+      .WillOnce(RunOnceCallback<1>(true));  // Phishing detected
+
+  // Observing an OTP field is supposed to trigger an SMS OTP request.
+  AddFormWithOtpField();
+
+  base::test::TestFuture<const std::vector<std::string>> future;
+  otp_manager.GetOtpSuggestions(future.GetCallback());
+
+  EXPECT_TRUE(future.Get().empty());
+}
+
+// Tests that suggestions are returned if the phishing check returns false.
+TEST_F(OtpManagerImplTest, GetOtpSuggestions_PhishingCheckReturnsFalse) {
+  OtpManagerImpl otp_manager(autofill_manager(), &one_time_token_service_);
+
+  // Prepare the handling of SMS requests from the SMS backend.
+  one_time_tokens::OtpFetchReply reply = GetDefaultOtpFetchReply();
+  EXPECT_CALL(sms_otp_backend_, RetrieveSmsOtp)
+      .WillOnce(RunOnceCallback<0>(reply));
+  EXPECT_CALL(otp_phish_guard_delegate(), StartOtpPhishGuardCheck)
+      .WillOnce(RunOnceCallback<1>(false));  // No phishing
+
+  // Observing an OTP field is supposed to trigger an SMS OTP request.
+  AddFormWithOtpField();
+
+  base::test::TestFuture<const std::vector<std::string>> future;
+  otp_manager.GetOtpSuggestions(future.GetCallback());
+
+  ASSERT_EQ(future.Get().size(), 1u);
+  EXPECT_EQ(future.Get()[0], reply.otp_value->value());
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/integrators/one_time_tokens/otp_phish_guard_delegate.h b/components/autofill/core/browser/integrators/one_time_tokens/otp_phish_guard_delegate.h
new file mode 100644
index 0000000..2d28255
--- /dev/null
+++ b/components/autofill/core/browser/integrators/one_time_tokens/otp_phish_guard_delegate.h
@@ -0,0 +1,28 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_INTEGRATORS_ONE_TIME_TOKENS_OTP_PHISH_GUARD_DELEGATE_H_
+#define COMPONENTS_AUTOFILL_CORE_BROWSER_INTEGRATORS_ONE_TIME_TOKENS_OTP_PHISH_GUARD_DELEGATE_H_
+
+#include "base/functional/callback.h"
+#include "url/gurl.h"
+
+namespace autofill {
+
+// A delegate for checking if a given URL is a phishing site before unmasking
+// an OTP.
+class OtpPhishGuardDelegate {
+ public:
+  virtual ~OtpPhishGuardDelegate() = default;
+
+  // Checks if the given `url` is a phishing site. `callback` is run with the
+  // result.
+  virtual void StartOtpPhishGuardCheck(
+      const GURL& url,
+      base::OnceCallback<void(bool)> callback) = 0;
+};
+
+}  // namespace autofill
+
+#endif  // COMPONENTS_AUTOFILL_CORE_BROWSER_INTEGRATORS_ONE_TIME_TOKENS_OTP_PHISH_GUARD_DELEGATE_H_