VYPR
High severity8.8NVD Advisory· Published Apr 3, 2026· Updated Apr 15, 2026

CVE-2026-33175

CVE-2026-33175

Description

OAuthenticator is software that allows OAuth2 identity providers to be plugged in and used with JupyterHub. Prior to version 17.4.0, an authentication bypass vulnerability in oauthenticator allows an attacker with an unverified email address on an Auth0 tenant to login to JupyterHub. When email is used as the usrname_claim, this gives users control over their username and the possibility of account takeover. This issue has been patched in version 17.4.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
oauthenticatorPyPI
< 17.4.017.4.0

Affected products

1

Patches

1
f0c7002dc36e

Merge commit from fork

2 files changed · +78 5
  • oauthenticator/auth0.py+36 1 modified
    @@ -5,7 +5,8 @@
     import os
     
     from jupyterhub.auth import LocalAuthenticator
    -from traitlets import Unicode, default
    +from tornado.web import HTTPError
    +from traitlets import Bool, Unicode, default
     
     from .oauth2 import OAuthenticator
     
    @@ -56,6 +57,15 @@ def _auth0_subdomain_default(self):
             # This is allowed to be empty unless auth0_domain is not supplied either
             return os.getenv("AUTH0_SUBDOMAIN", "")
     
    +    allow_unverified_email = Bool(
    +        False,
    +        config=True,
    +        help="""
    +        Allow login with unverified email.
    +        Not advisable, except for testing purposes.
    +        """,
    +    )
    +
         @default("logout_redirect_url")
         def _logout_redirect_url_default(self):
             return f"https://{self.auth0_domain}/v2/logout"
    @@ -72,6 +82,31 @@ def _token_url_default(self):
         def _userdata_url_default(self):
             return f"https://{self.auth0_domain}/userinfo"
     
    +    async def check_allowed(self, username, auth_model):
    +        # A workaround for JupyterHub < 5.0 described in
    +        # https://github.com/jupyterhub/oauthenticator/issues/621
    +        if auth_model is None:
    +            return True
    +
    +        # before considering allowing a username by being recognized in a list
    +        # of usernames or similar, we must ensure that the authenticated user
    +        # has a verified email and is part of hosted_domain if configured.
    +        user_info = auth_model["auth_state"][self.user_auth_state_key]
    +        user_email = user_info["email"]
    +
    +        if not user_info.get("email_verified"):
    +            if self.allow_unverified_email:
    +                message = (
    +                    f"Allowing login for {username} with unverified email {user_email}"
    +                )
    +                self.log.warning(message)
    +            else:
    +                message = f"Login with unverified email {user_email} is not allowed"
    +                self.log.warning(message)
    +                raise HTTPError(403, message)
    +
    +        return await super().check_allowed(username, auth_model)
    +
         # _deprecated_oauth_aliases is used by deprecation logic in OAuthenticator
         _deprecated_oauth_aliases = {
             "username_key": ("username_claim", "16.0.0"),
    
  • oauthenticator/tests/test_auth0.py+42 4 modified
    @@ -24,13 +24,17 @@ def auth0_client(client):
         return client
     
     
    -def user_model():
    +def user_model(extra=None):
         """Return a user model"""
    -    return {
    +    model = {
             "email": "user1@example.com",
             "name": "user1",
             "groups": ["group1"],
    +        "email_verified": True,
         }
    +    if extra:
    +        model.update(extra)
    +    return model
     
     
     @mark.parametrize(
    @@ -113,7 +117,6 @@ async def test_auth0(
         expect_allowed,
         expect_admin,
     ):
    -    print(f"Running test variation id {test_variation_id}")
         c = Config()
         c.Auth0OAuthenticator = Config(class_config)
         c.Auth0OAuthenticator.auth0_domain = AUTH0_DOMAIN
    @@ -138,7 +141,42 @@ async def test_auth0(
             assert user_info == handled_user_model
             assert auth_model["name"] == user_info[authenticator.username_claim]
         else:
    -        assert auth_model == None
    +        assert auth_model is None
    +
    +
    +@mark.parametrize(
    +    "email_verified, allow_unverified, expect_allowed",
    +    [
    +        (True, False, True),
    +        (False, False, False),
    +        (False, True, True),
    +    ],
    +)
    +async def test_email_verified(
    +    auth0_client, email_verified, allow_unverified, expect_allowed
    +):
    +    c = Config()
    +    c.Auth0OAuthenticator = Config()
    +    c.Auth0OAuthenticator.auth0_domain = AUTH0_DOMAIN
    +    c.Auth0OAuthenticator.username_claim = "email"
    +    c.Auth0OAuthenticator.allow_all = True
    +    if allow_unverified:
    +        c.Auth0OAuthenticator.allow_unverified_email = allow_unverified
    +    authenticator = Auth0OAuthenticator(config=c)
    +    assert authenticator.allow_unverified_email == allow_unverified
    +
    +    handled_user_model = user_model({"email_verified": email_verified})
    +    handler = auth0_client.handler_for_user(handled_user_model)
    +
    +    auth_model = await authenticator.get_authenticated_user(handler, None)
    +    assert auth_model is not None
    +    if expect_allowed:
    +        allowed = await authenticator.check_allowed(auth_model["name"], auth_model)
    +        assert allowed
    +    else:
    +        with raises(web.HTTPError) as exc_info:
    +            await authenticator.check_allowed(auth_model["name"], auth_model)
    +        assert exc_info.value.status_code == 403
     
     
     async def test_custom_logout(monkeypatch):
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.