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 <ernn@google.com>
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 <droger@chromium.org>
Commit-Queue: Ernest Nguyen Hung <ernn@google.com>
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(
+ "account@gmail.com", {.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(
+ "account@gmail.com", {.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(
+ "account@gmail.com", {.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("account@gmail.com",
{.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(
+ "account@gmail.com", {.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(
+ "account@gmail.com", {.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(
+ "account@gmail.com", {.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("account@gmail.com",
{.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