-
Notifications
You must be signed in to change notification settings - Fork 4.7k
[WEB-7778] fix(security): reject unverified OAuth provider emails to prevent ATO (Cluster E) #9289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: preview
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -102,6 +102,14 @@ def set_token_data(self): | |
|
|
||
| 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): | ||
|
Comment on lines
+105
to
+108
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n apps/api/plane/authentication/provider/oauth/google.py | sed -n '95,120p'Repository: makeplane/plane Length of output: 1555 🌐 Web query:
💡 Result: The standard field for verifying an email address in the Google UserInfo endpoint is Citations:
🏁 Script executed: git log --oneline -n 20 -- apps/api/plane/authentication/provider/oauth/google.pyRepository: makeplane/plane Length of output: 150 🏁 Script executed: rg "verified_email" --type py -B 3 -A 3Repository: makeplane/plane Length of output: 948 🏁 Script executed: find apps/api/plane/authentication/provider/oauth -name "*.py" -type f | head -10Repository: makeplane/plane Length of output: 335 🏁 Script executed: rg "def set_user_data" apps/api/plane/authentication/provider/oauth/ -A 10Repository: makeplane/plane Length of output: 4645 🌐 Web query:
💡 Result: The verified_email field (often represented as email_verified in OpenID Connect) in the Google OAuth2 userinfo endpoint is not strictly mandatory in the sense that it is always guaranteed to be present in every response, though it is standard when the appropriate scopes are requested [1][2][3]. Specifically, the following details apply: 1. Scope Dependency: The field is generally included when you request the email scope [1][4][3]. Official documentation states that if the email scope value is present, the response (or ID Token) includes the email and email_verified claims [1][3]. 2. Availability: While it is standard, authoritative sources and community discussions indicate that developers should not treat these claims as guaranteed in every possible scenario [5][2]. It is best practice to design your application to handle cases where these fields might be absent [5][2]. 3. Terminology: Be aware that different Google APIs use slightly different naming conventions. The OpenID Connect UserInfo endpoint typically uses the field name email_verified [1][3], while some older Google OAuth2 v2 API client libraries and documentation refer to it as verified_email [6][7][8]. In summary, while you should expect verified_email/email_verified when requesting the email scope, it should be treated as optional or conditional in your application logic to ensure robustness [5][2]. Always rely on the sub (subject) field as the stable, unique identifier for the user [1][3]. Citations:
Require explicit verification signal; reject when Google omits Line 108 treats a missing verification claim as trusted with the Suggested fix- # 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):
+ # Reject unverified emails — an attacker-controlled provider could otherwise assert
+ # any email to match an existing account (GHSA-7j95-vh8g-f365).
+ if user_info_response.get("verified_email") is not True:🤖 Prompt for AI Agents |
||
| raise AuthenticationException( | ||
| error_code=AUTHENTICATION_ERROR_CODES["OAUTH_PROVIDER_UNVERIFIED_EMAIL"], | ||
| error_message="OAUTH_PROVIDER_UNVERIFIED_EMAIL", | ||
| ) | ||
| user_data = { | ||
| "email": user_info_response.get("email"), | ||
| "user": { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
For the Gitea versions supported by Plane, does an OAuth2 access token requested with onlyopenid email profileauthorizeGET /api/v1/user/emails, or is a granular user scope such asread:userrequired?💡 Result:
No, an OAuth2 access token requested with only the openid, email, and profile scopes does not authorize access to the GET /api/v1/user/emails endpoint in Gitea. In Gitea’s API routing, the /api/v1/user/emails endpoint is protected and requires a token with the user scope (specifically, the user scope category) [1][2]. The openid, email, and profile scopes are standard OIDC scopes intended for identity and basic profile information (such as user ID, email address, and name) [3][4]. They do not grant authorization for Gitea's administrative or resource-specific API endpoints [1][2]. To successfully call GET /api/v1/user/emails, the OAuth2 client must explicitly request a granular scope that includes user-level access (e.g., read:user) [5][6][4]. While Gitea has evolved to support granular scopes for OAuth2, these must be requested during the authorization flow, and the openid, email, and profile scopes are insufficient to bypass the authorization checks required for /api/v1/user/emails [5][3][1].
Citations:
🏁 Script executed:
Repository: makeplane/plane
Length of output: 481
🏁 Script executed:
Repository: makeplane/plane
Length of output: 5170
🏁 Script executed:
# Check the full gitea.py file to understand the current scope configuration wc -l apps/api/plane/authentication/provider/oauth/gitea.pyRepository: makeplane/plane
Length of output: 117
🏁 Script executed:
# Read the gitea.py file to see the __get_email() implementation cat -n apps/api/plane/authentication/provider/oauth/gitea.pyRepository: makeplane/plane
Length of output: 8390
Add
read:userscope to Gitea OAuth configuration.The
__get_email()method at line 161 callsGET /api/v1/user/emails, which requires theread:usergranular scope in Gitea's OAuth2. The current scope set (openid email profile) does not authorize this endpoint—these are standard OIDC scopes for identity and basic profile info only. Update line 22 to include the required scope:scope = "openid email profile read:user", or switch to an OIDCemail_verifiedclaim if available in your Gitea version to avoid the additional API call.🤖 Prompt for AI Agents