From 3daec9476bb154f50bb46734766c4755a42caa89 Mon Sep 17 00:00:00 2001 From: Manish Gupta Date: Wed, 24 Jun 2026 12:47:16 +0530 Subject: [PATCH] fix(security): add read:user scope to Gitea; fail-closed on absent Google verified_email MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gitea's /api/v1/user/emails endpoint requires the read:user granular scope — openid+email+profile alone is insufficient and __get_email() would return a 401/403. Add read:user to the scope string. Google: change default from True to fail-closed (is not True) so a userinfo response that omits verified_email is rejected rather than trusted. The service-account justification was incorrect — service accounts do not go through the interactive OAuth2 callback flow. Co-authored-by: Plane AI --- apps/api/plane/authentication/provider/oauth/gitea.py | 2 +- apps/api/plane/authentication/provider/oauth/google.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/api/plane/authentication/provider/oauth/gitea.py b/apps/api/plane/authentication/provider/oauth/gitea.py index ba9119194d..7a1392854d 100644 --- a/apps/api/plane/authentication/provider/oauth/gitea.py +++ b/apps/api/plane/authentication/provider/oauth/gitea.py @@ -19,7 +19,7 @@ from plane.authentication.adapter.error import ( class GiteaOAuthProvider(OauthAdapter): provider = "gitea" - scope = "openid email profile" + scope = "openid email profile read:user" def __init__(self, request, code=None, state=None, callback=None): (GITEA_CLIENT_ID, GITEA_CLIENT_SECRET, GITEA_HOST) = get_configuration_value( diff --git a/apps/api/plane/authentication/provider/oauth/google.py b/apps/api/plane/authentication/provider/oauth/google.py index 43572ae9b2..ce15a05065 100644 --- a/apps/api/plane/authentication/provider/oauth/google.py +++ b/apps/api/plane/authentication/provider/oauth/google.py @@ -103,9 +103,9 @@ class GoogleOAuthProvider(OauthAdapter): def set_user_data(self): user_info_response = self.get_user_response() # Reject unverified emails — an attacker-controlled provider could otherwise assert - # any email to match an existing account (GHSA-7j95-vh8g-f365). Default True so - # service accounts that omit the field are not broken. - if not user_info_response.get("verified_email", True): + # any email to match an existing account (GHSA-7j95-vh8g-f365). Fail closed: treat + # an absent verified_email claim the same as verified_email=false. + if user_info_response.get("verified_email") is not True: raise AuthenticationException( error_code=AUTHENTICATION_ERROR_CODES["OAUTH_PROVIDER_UNVERIFIED_EMAIL"], error_message="OAUTH_PROVIDER_UNVERIFIED_EMAIL",