Chrome · SignIn › Signin
CVE-2026-106312
Authorization Error in SignIn (Signin)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
chrome/browser/extensions/api/identity/identity_apitest.ccchrome/browser/extensions/api/identity/identity_get_auth_token_function.cc
Patch
From 7e482c0020ad26c962927f2e473ccbcbcdf93706 Mon Sep 17 00:00:00 2001 From: Ernest Nguyen Hung <[email protected]> Date: Wed, 09 Sep 2026 03:55:03 -0700 Subject: [PATCH] [signin] Update `chrome.identity.getAuthToken` to check for access When `chrome.identity.getAuthToken` receives an explicit `account.id` (or has a cached Gaia ID for the extension), resolve the account against `IdentityAPI::GetAccountsWithRefreshTokensForExtensions` instead of `IdentityManager` directly. Fixed: 502111211 Change-Id: I8947eb3bbbe241336012d0160d2884820e27d23a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8378261 Reviewed-by: David Roger <[email protected]> Commit-Queue: Ernest Nguyen Hung <[email protected]> Cr-Commit-Position: refs/heads/main@{#1694485} --- diff --git a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc index 3d5d593..c4af70f 100644 --- a/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc +++ b/chrome/browser/extensions/api/identity/identity_get_auth_token_function.cc @@ -12,6 +12,7 @@ #include <vector> #include "base/check.h" +#include "base/check_deref.h" #include "base/functional/bind.h" #include "base/functional/callback.h" #include "base/location.h" @@ -357,20 +358,25 @@ const GaiaId& gaia_id) { refresh_tokens_loaded_waiter_.reset(); + IdentityAPI& identity_api = CHECK_DEREF( + CHECK_DEREF(IdentityAPI::GetFactoryInstance()).Get(GetProfile())); + selected_gaia_id_ = gaia_id; if (gaia_id.empty()) { - selected_gaia_id_ = IdentityAPI::GetFactoryInstance() - ->Get(GetProfile()) - ->GetGaiaIdForExtension(token_key_.extension_id) - .value_or(GaiaId()); + selected_gaia_id_ = + identity_api.GetGaiaIdForExtension(token_key_.extension_id) + .value_or(GaiaId()); } CoreAccountInfo selected_account; if (!selected_gaia_id_.empty()) { - // TODO(msalama): Check has access to accounts. - selected_account = IdentityManagerFactory::GetForProfile(GetProfile()) - ->FindExtendedAccountInfoByGaiaId(selected_gaia_id_) - .GetCoreAccountInfo(); + const std::vector<CoreAccountInfo> accounts = + identity_api.GetAccountsWithRefreshTokensForExtensions(); + auto it = + std::ranges::find(accounts, selected_gaia_id_, &CoreAccountInfo::gaia); + if (it != accounts.end()) { + selected_account = *it; + } } else { selected_account = GetSigninPrimaryAccount(GetProfile()); } diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc index aa0688b..f1f3b69 100644 --- a/chrome/browser/extensions/api/identity/identity_apitest.cc +++ b/chrome/browser/extensions/api/identity/identity_apitest.cc @@ -1628,6 +1628,126 @@ } IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, + SignedInWebOnlyNonInteractiveWithAccount) { + const AccountInfo account_info = identity_test_env()->MakeAccountAvailable( + "[email protected]", {.set_cookie = true}); + ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount( + signin::ConsentLevel::kSignin)); + ASSERT_FALSE(identity_test_env() + ->identity_manager() + ->GetAccountsWithRefreshTokens() + .empty()); + + scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); + scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); + func->set_extension(extension.get()); + + const std::string args = + base::StringPrintf(R"([{"interactive": false, "account": {"id": "%s"}}])", + account_info.GetGaiaId().ToString().c_str()); + const std::string error = + utils::RunFunctionAndReturnError(func.get(), args, profile()); + EXPECT_EQ(error, std::string(errors::kUserNotSignedIn)); + EXPECT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount( + signin::ConsentLevel::kSignin)); + + EXPECT_FALSE(func->login_ui_shown()); + EXPECT_FALSE(func->scope_ui_shown()); + histogram_tester()->ExpectUniqueSample( + kGetAuthTokenResultHistogramName, + IdentityGetAuthTokenError::State::kUserNotSignedIn, 1); +} + +IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, + SignedInWebOnlyAcceptPromptWithAccount) { + const AccountInfo account_info = identity_test_env()->MakeAccountAvailable( + "[email protected]", {.set_cookie = true}); + ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount( + signin::ConsentLevel::kSignin)); + ASSERT_FALSE(identity_test_env() + ->identity_manager() + ->GetAccountsWithRefreshTokens() + .empty()); + + scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); + scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); + func->set_extension(extension.get()); + func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); + + views::NamedWidgetShownWaiter widget_waiter( + views::test::AnyWidgetTestPasskey{}, + "ChromeSigninChoiceForExtensionsPrompt"); + + const std::string args = + base::StringPrintf(R"([{"interactive": true, "account": {"id": "%s"}}])", + account_info.GetGaiaId().ToString().c_str()); + RunFunctionAsync(func.get(), args); + views::Widget* confirmation_prompt = widget_waiter.WaitIfNeededAndGet(); + ASSERT_NE(confirmation_prompt, nullptr); + views::DialogDelegate* dialog_delegate = + confirmation_prompt->widget_delegate()->AsDialogDelegate(); + ASSERT_NE(dialog_delegate, nullptr); + dialog_delegate->AcceptDialog(); + + std::string access_token; + std::set<std::string> granted_scopes; + WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes); + EXPECT_EQ(access_token, std::string(kAccessToken)); + EXPECT_EQ(granted_scopes, func->GetExtensionTokenKeyForTest()->scopes); + EXPECT_EQ(account_info.GetAccountId(), + identity_test_env()->identity_manager()->GetPrimaryAccountId( + signin::ConsentLevel::kSignin)); + + EXPECT_FALSE(func->login_ui_shown()); + EXPECT_FALSE(func->scope_ui_shown()); + histogram_tester()->ExpectUniqueSample( + kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone, + 1); +} + +IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, + SignedInWebOnlyDeclinePromptWithAccount) { + const AccountInfo account_info = identity_test_env()->MakeAccountAvailable( + "[email protected]", {.set_cookie = true}); + ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount( + signin::ConsentLevel::kSignin)); + ASSERT_FALSE(identity_test_env() + ->identity_manager() + ->GetAccountsWithRefreshTokens() + .empty()); + + scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES)); + scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction()); + func->set_extension(extension.get()); + func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS); + + views::NamedWidgetShownWaiter widget_waiter( + views::test::AnyWidgetTestPasskey{}, + "ChromeSigninChoiceForExtensionsPrompt"); + + const std::string args = + base::StringPrintf(R"([{"interactive": true, "account": {"id": "%s"}}])", + account_info.GetGaiaId().ToString().c_str()); + RunFunctionAsync(func.get(), args); + views::Widget* confirmation_prompt = widget_waiter.WaitIfNeededAndGet(); + ASSERT_NE(confirmation_prompt, nullptr); + views::DialogDelegate* dialog_delegate = + confirmation_prompt->widget_delegate()->AsDialogDelegate(); + ASSERT_NE(dialog_delegate, nullptr); + dialog_delegate->CancelDialog(); + + EXPECT_EQ(WaitForError(func.get()), std::string(errors::kUserNotSignedIn)); + EXPECT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount( + signin::ConsentLevel::kSignin)); + + EXPECT_FALSE(func->login_ui_shown()); + EXPECT_FALSE(func->scope_ui_shown()); + histogram_tester()->ExpectUniqueSample( + kGetAuthTokenResultHistogramName, + IdentityGetAuthTokenError::State::kSignInFailed, 1); +} + +IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, SignedInWebOnlyAcceptPromptMultipleFunctions) { identity_test_env()->MakeAccountAvailable("[email protected]", {.set_cookie = true});
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index aa0688b..f1f3b69 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -1628,6 +1628,126 @@
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
+ SignedInWebOnlyNonInteractiveWithAccount) {
+ const AccountInfo account_info = identity_test_env()->MakeAccountAvailable(
+ "[email protected]", {.set_cookie = true});
+ ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+ signin::ConsentLevel::kSignin));
+ ASSERT_FALSE(identity_test_env()
+ ->identity_manager()
+ ->GetAccountsWithRefreshTokens()
+ .empty());
+
+ scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
+ scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
+ func->set_extension(extension.get());
+
+ const std::string args =
+ base::StringPrintf(R"([{"interactive": false, "account": {"id": "%s"}}])",
+ account_info.GetGaiaId().ToString().c_str());
+ const std::string error =
+ utils::RunFunctionAndReturnError(func.get(), args, profile());
+ EXPECT_EQ(error, std::string(errors::kUserNotSignedIn));
+ EXPECT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+ signin::ConsentLevel::kSignin));
+
+ EXPECT_FALSE(func->login_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
+ histogram_tester()->ExpectUniqueSample(
+ kGetAuthTokenResultHistogramName,
+ IdentityGetAuthTokenError::State::kUserNotSignedIn, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
+ SignedInWebOnlyAcceptPromptWithAccount) {
+ const AccountInfo account_info = identity_test_env()->MakeAccountAvailable(
+ "[email protected]", {.set_cookie = true});
+ ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+ signin::ConsentLevel::kSignin));
+ ASSERT_FALSE(identity_test_env()
+ ->identity_manager()
+ ->GetAccountsWithRefreshTokens()
+ .empty());
+
+ scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
+ scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
+ func->set_extension(extension.get());
+ func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
+
+ views::NamedWidgetShownWaiter widget_waiter(
+ views::test::AnyWidgetTestPasskey{},
+ "ChromeSigninChoiceForExtensionsPrompt");
+
+ const std::string args =
+ base::StringPrintf(R"([{"interactive": true, "account": {"id": "%s"}}])",
+ account_info.GetGaiaId().ToString().c_str());
+ RunFunctionAsync(func.get(), args);
+ views::Widget* confirmation_prompt = widget_waiter.WaitIfNeededAndGet();
+ ASSERT_NE(confirmation_prompt, nullptr);
+ views::DialogDelegate* dialog_delegate =
+ confirmation_prompt->widget_delegate()->AsDialogDelegate();
+ ASSERT_NE(dialog_delegate, nullptr);
+ dialog_delegate->AcceptDialog();
+
+ std::string access_token;
+ std::set<std::string> granted_scopes;
+ WaitForGetAuthTokenResults(func.get(), &access_token, &granted_scopes);
+ EXPECT_EQ(access_token, std::string(kAccessToken));
+ EXPECT_EQ(granted_scopes, func->GetExtensionTokenKeyForTest()->scopes);
+ EXPECT_EQ(account_info.GetAccountId(),
+ identity_test_env()->identity_manager()->GetPrimaryAccountId(
+ signin::ConsentLevel::kSignin));
+
+ EXPECT_FALSE(func->login_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
+ histogram_tester()->ExpectUniqueSample(
+ kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone,
+ 1);
+}
+
+IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
+ SignedInWebOnlyDeclinePromptWithAccount) {
+ const AccountInfo account_info = identity_test_env()->MakeAccountAvailable(
+ "[email protected]", {.set_cookie = true});
+ ASSERT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+ signin::ConsentLevel::kSignin));
+ ASSERT_FALSE(identity_test_env()
+ ->identity_manager()
+ ->GetAccountsWithRefreshTokens()
+ .empty());
+
+ scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
+ scoped_refptr<FakeGetAuthTokenFunction> func(new FakeGetAuthTokenFunction());
+ func->set_extension(extension.get());
+ func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
+
+ views::NamedWidgetShownWaiter widget_waiter(
+ views::test::AnyWidgetTestPasskey{},
+ "ChromeSigninChoiceForExtensionsPrompt");
+
+ const std::string args =
+ base::StringPrintf(R"([{"interactive": true, "account": {"id": "%s"}}])",
+ account_info.GetGaiaId().ToString().c_str());
+ RunFunctionAsync(func.get(), args);
+ views::Widget* confirmation_prompt = widget_waiter.WaitIfNeededAndGet();
+ ASSERT_NE(confirmation_prompt, nullptr);
+ views::DialogDelegate* dialog_delegate =
+ confirmation_prompt->widget_delegate()->AsDialogDelegate();
+ ASSERT_NE(dialog_delegate, nullptr);
+ dialog_delegate->CancelDialog();
+
+ EXPECT_EQ(WaitForError(func.get()), std::string(errors::kUserNotSignedIn));
+ EXPECT_FALSE(identity_test_env()->identity_manager()->HasPrimaryAccount(
+ signin::ConsentLevel::kSignin));
+
+ EXPECT_FALSE(func->login_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
+ histogram_tester()->ExpectUniqueSample(
+ kGetAuthTokenResultHistogramName,
+ IdentityGetAuthTokenError::State::kSignInFailed, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
SignedInWebOnlyAcceptPromptMultipleFunctions) {
identity_test_env()->MakeAccountAvailable("[email protected]",
{.set_cookie = true});
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page