VYPR
High severity8.1NVD Advisory· Published Mar 6, 2026· Updated Apr 21, 2026

CVE-2026-28681

CVE-2026-28681

Description

Internet Routing Registry daemon version 4 is an IRR database server, processing IRR objects in the RPSL format. From version 4.4.0 to before version 4.4.5 and from version 4.5.0 to before version 4.5.1, an attacker can manipulate the HTTP Host header on a password reset or account creation request. The confirmation link in the resulting email can then point to an attacker-controlled domain. Opening the link in the email is sufficient to pass the token to the attacker, who can then use it on the real IRRD instance to take over the account. A compromised account can then be used to modify RPSL objects maintained by the account's mntners and perform other account actions. If the user had two-factor authentication configured, which is required for users with override access, an attacker is not able to log in, even after successfully resetting the password. This issue has been patched in versions 4.4.5 and 4.5.1.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
irrdPyPI
>= 4.4.0, < 4.4.54.4.5
irrdPyPI
>= 4.5.0, < 4.5.14.5.1

Affected products

1

Patches

2
8408e0f1b9f4

[4.4.x] Add Host header invalidation and invalidate all pw reset tokens

https://github.com/irrdnet/irrdSasha RomijnMar 1, 2026via ghsa
5 files changed · +127 5
  • irrd/integration_tests/run.py+32 5 modified
    @@ -579,8 +579,8 @@ def test_irrd_integration(self, tmpdir):
             self.check_graphql()
     
         def check_http(self):
    -        status1 = requests.get(f"http://127.0.0.1:{self.port_http1}/v1/status/")
    -        status2 = requests.get(f"http://127.0.0.1:{self.port_http2}/v1/status/")
    +        status1 = requests.get(f"http://localhost:{self.port_http1}/v1/status/")
    +        status2 = requests.get(f"http://localhost:{self.port_http2}/v1/status/")
             assert status1.status_code == 200
             assert status2.status_code == 200
             assert "IRRD version" in status1.text
    @@ -592,8 +592,14 @@ def check_http(self):
             assert "Authoritative: Yes" in status1.text
             assert "Authoritative: Yes" not in status2.text
     
    +        # GHSA-22m3-c7vp-49fj CVE-2026-28681
    +        wrong_host = requests.get(
    +            f"http://localhost:{self.port_http1}/v1/status/", headers={"Host": "attacker.com"}
    +        )
    +        assert wrong_host.status_code == 400
    +
         def check_graphql(self):
    -        client = GraphqlClient(endpoint=f"http://127.0.0.1:{self.port_http1}/graphql/")
    +        client = GraphqlClient(endpoint=f"http://localhost:{self.port_http1}/graphql/")
             # Regular rpslObjects query including journal and several references
             query = """query {
               rpslObjects(rpslPk: "PERSON-TEST") {
    @@ -838,8 +844,6 @@ def _start_irrds(self):
                         "http": {
                             "status_access_list": "localhost",
                             "interface": "::1",
    -                        "port": 8080,
    -                        "url": "https://localhost:8080/",
                         },
                         "whois": {"interface": "::1", "max_connections": 10, "port": 8043},
                     },
    @@ -887,6 +891,7 @@ def _start_irrds(self):
             config1["irrd"]["redis_url"] = self.redis_url1
             config1["irrd"]["server"]["http"]["interface"] = "127.0.0.1"  # #306
             config1["irrd"]["server"]["http"]["port"] = self.port_http1
    +        config1["irrd"]["server"]["http"]["url"] = f"https://localhost:{self.port_http1}/"
             config1["irrd"]["server"]["whois"]["interface"] = "127.0.0.1"
             config1["irrd"]["server"]["whois"]["port"] = self.port_whois1
             config1["irrd"]["auth"]["gnupg_keyring"] = str(self.tmpdir) + "/gnupg1"
    @@ -907,6 +912,7 @@ def _start_irrds(self):
             config2["irrd"]["database_url"] = self.database_url2
             config2["irrd"]["redis_url"] = self.redis_url2
             config2["irrd"]["server"]["http"]["port"] = self.port_http2
    +        config1["irrd"]["server"]["http"]["url"] = f"https://localhost:{self.port_http2}/"
             config2["irrd"]["server"]["whois"]["port"] = self.port_whois2
             config2["irrd"]["auth"]["gnupg_keyring"] = str(self.tmpdir) + "/gnupg2"
             config2["irrd"]["log"]["logfile_path"] = self.logfile2
    @@ -925,6 +931,27 @@ def _start_irrds(self):
             with open(self.config_path2, "w") as yaml_file:
                 yaml.safe_dump(config2, yaml_file)
     
    +<<<<<<< HEAD
    +=======
    +        config3 = base_config.copy()
    +        config3["irrd"]["piddir"] = self.piddir3
    +        config3["irrd"]["database_url"] = self.database_url3
    +        config3["irrd"]["redis_url"] = self.redis_url3
    +        config3["irrd"]["server"]["http"]["port"] = self.port_http3
    +        config1["irrd"]["server"]["http"]["url"] = f"https://localhost:{self.port_http3}/"
    +        config3["irrd"]["server"]["whois"]["port"] = self.port_whois3
    +        config3["irrd"]["auth"]["gnupg_keyring"] = str(self.tmpdir) + "/gnupg3"
    +        config3["irrd"]["log"]["logfile_path"] = self.logfile3
    +        config3["irrd"]["rpki"]["roa_source"] = None
    +        config3["irrd"]["sources"]["TEST"] = {
    +            "keep_journal": True,
    +            "nrtm4_client_notification_file_url": f"file://{self.nrtm4_dir2}{UPDATE_NOTIFICATION_FILENAME}",
    +            "nrtm4_client_initial_public_key": eckey_public_key_as_str(self.nrtm4_private_key),
    +        }
    +        with open(self.config_path3, "w") as yaml_file:
    +            yaml.safe_dump(config3, yaml_file)
    +
    +>>>>>>> 1045a2f (Add Host header invalidation and invalidate all pw reset tokens)
             self._prepare_database()
     
             assert not subprocess.call(["irrd/daemon/main.py", f"--config={self.config_path1}"])
    
  • irrd/server/http/app.py+4 0 modified
    @@ -2,6 +2,7 @@
     import os
     import signal
     from pathlib import Path
    +from urllib.parse import urlparse
     
     import limits
     from ariadne.asgi import GraphQL
    @@ -11,6 +12,7 @@
     from starlette.applications import Starlette
     from starlette.middleware import Middleware
     from starlette.middleware.sessions import SessionMiddleware
    +from starlette.middleware.trustedhost import TrustedHostMiddleware
     from starlette.responses import RedirectResponse
     from starlette.routing import Mount, Route, WebSocketRoute
     from starlette.staticfiles import StaticFiles
    @@ -144,13 +146,15 @@ def set_middleware(app):
         testing = os.environ.get("TESTING", False)
         if testing:
             logger.info("Running in testing mode, disabling CSRF.")
    +    allowed_host = urlparse(get_setting("server.http.url")).hostname
         app.user_middleware = [
             # Use asgi-log to work around https://github.com/encode/uvicorn/issues/1384
             Middleware(
                 AccessLoggerMiddleware,
                 logger=logger,
                 format='%(client_addr)s - "%(request_line)s" %(status_code)s - %(L)ss',
             ),
    +        Middleware(TrustedHostMiddleware, allowed_hosts=[allowed_host]),
             Middleware(MemoryTrimMiddleware),
             Middleware(SessionMiddleware, secret_key=secret_key_derive("web.session_middleware")),
             Middleware(
    
  • irrd/webui/auth/users.py+6 0 modified
    @@ -161,6 +161,12 @@ def validate_token(self, token: str) -> bool:
             except ValueError:
                 return False
     
    +<<<<<<< HEAD
         def _hash(self, expiry_days: Union[int, str]) -> bytes:
             hash_data = secret_key_derive("web.password_reset_token") + self.user_key + str(expiry_days)
    +=======
    +    def _hash(self, expiry_days: int | str) -> bytes:
    +        # https://github.com/irrdnet/irrd/security/advisories/GHSA-22m3-c7vp-49fj
    +        hash_data = secret_key_derive("web.password_reset_token2") + self.user_key + str(expiry_days)
    +>>>>>>> 1045a2f (Add Host header invalidation and invalidate all pw reset tokens)
             return hashlib.sha224(hash_data.encode("utf-8")).digest()
    
  • poetry.lock+79 0 modified
    @@ -1,4 +1,20 @@
    +<<<<<<< HEAD
     # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
    +=======
    +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
    +
    +[[package]]
    +name = "aiohappyeyeballs"
    +version = "2.6.1"
    +description = "Happy Eyeballs for asyncio"
    +optional = false
    +python-versions = ">=3.9"
    +groups = ["dev"]
    +files = [
    +    {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"},
    +    {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"},
    +]
    +>>>>>>> 1045a2f (Add Host header invalidation and invalidate all pw reset tokens)
     
     [[package]]
     name = "aiohttp"
    @@ -292,7 +308,12 @@ name = "attrs"
     version = "23.1.0"
     description = "Classes Without Boilerplate"
     optional = false
    +<<<<<<< HEAD
     python-versions = ">=3.7"
    +=======
    +python-versions = ">=3.9"
    +groups = ["main", "dev"]
    +>>>>>>> 1045a2f (Add Host header invalidation and invalidate all pw reset tokens)
     files = [
         {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"},
         {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"},
    @@ -1971,6 +1992,33 @@ files = [
         {file = "py-radix-sr-1.0.0.post1.tar.gz", hash = "sha256:e0c0f922380856bbdf785c701f67661f1b5c5cb6779308532ce3c27a6204cd7d"},
     ]
     
    +[[package]]
    +name = "pyasn1"
    +version = "0.6.2"
    +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
    +optional = false
    +python-versions = ">=3.8"
    +groups = ["main"]
    +files = [
    +    {file = "pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf"},
    +    {file = "pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b"},
    +]
    +
    +[[package]]
    +name = "pyasn1-modules"
    +version = "0.4.2"
    +description = "A collection of ASN.1-based protocols modules"
    +optional = false
    +python-versions = ">=3.8"
    +groups = ["main"]
    +files = [
    +    {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"},
    +    {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"},
    +]
    +
    +[package.dependencies]
    +pyasn1 = ">=0.6.1,<0.7.0"
    +
     [[package]]
     name = "pycparser"
     version = "2.21"
    @@ -2479,6 +2527,31 @@ files = [
         {file = "ruff-0.0.252.tar.gz", hash = "sha256:6992611ab7bdbe7204e4831c95ddd3febfeece2e6f5e44bbed044454c7db0f63"},
     ]
     
    +[[package]]
    +name = "service-identity"
    +version = "24.2.0"
    +description = "Service identity verification for pyOpenSSL & cryptography."
    +optional = false
    +python-versions = ">=3.8"
    +groups = ["main"]
    +files = [
    +    {file = "service_identity-24.2.0-py3-none-any.whl", hash = "sha256:6b047fbd8a84fd0bb0d55ebce4031e400562b9196e1e0d3e0fe2b8a59f6d4a85"},
    +    {file = "service_identity-24.2.0.tar.gz", hash = "sha256:b8683ba13f0d39c6cd5d625d2c5f65421d6d707b013b375c355751557cbe8e09"},
    +]
    +
    +[package.dependencies]
    +attrs = ">=19.1.0"
    +cryptography = "*"
    +pyasn1 = "*"
    +pyasn1-modules = "*"
    +
    +[package.extras]
    +dev = ["coverage[toml] (>=5.0.2)", "idna", "mypy", "pyopenssl", "pytest", "types-pyopenssl"]
    +docs = ["furo", "myst-parser", "pyopenssl", "sphinx", "sphinx-notfound-page"]
    +idna = ["idna"]
    +mypy = ["idna", "mypy", "types-pyopenssl"]
    +tests = ["coverage[toml] (>=5.0.2)", "pytest"]
    +
     [[package]]
     name = "setproctitle"
     version = "1.3.2"
    @@ -3559,6 +3632,12 @@ files = [
     ]
     
     [metadata]
    +<<<<<<< HEAD
     lock-version = "2.0"
     python-versions = "^3.8"
     content-hash = "8f62be1fc5e4d30f33c11b88106dc10a1d7afce8ef4b77cc990b933990eab825"
    +=======
    +lock-version = "2.1"
    +python-versions = ">3.10.0,<4.0"
    +content-hash = "f270f379f6f1d7f81bdc5302a35c99fc4325985b285d5c8799c7462e764d7c66"
    +>>>>>>> 1045a2f (Add Host header invalidation and invalidate all pw reset tokens)
    
  • pyproject.toml+6 0 modified
    @@ -63,6 +63,12 @@ zxcvbn = "4.4.28"
     wtforms-bootstrap5 = "0.2.3"
     email-validator = "2.0.0post2"
     asgi-logger = "0.1.0"
    +<<<<<<< HEAD
    +=======
    +joserfc = "1.6.3"
    +time-machine = "3.2.0"
    +service-identity = "^24.2.0"
    +>>>>>>> 1045a2f (Add Host header invalidation and invalidate all pw reset tokens)
     
     [tool.poetry.group.dev.dependencies]
     pytest = "^7.2.1"
    
cf62df4a49d3

[4.5.x] Add Host header invalidation and invalidate all pw reset tokens

https://github.com/irrdnet/irrdSasha RomijnMar 1, 2026via ghsa
5 files changed · +73 8
  • irrd/integration_tests/run.py+12 5 modified
    @@ -581,8 +581,8 @@ def test_irrd_integration(self, tmpdir):
             self.check_graphql()
     
         def check_http(self):
    -        status1 = requests.get(f"http://127.0.0.1:{self.port_http1}/v1/status/")
    -        status2 = requests.get(f"http://127.0.0.1:{self.port_http2}/v1/status/")
    +        status1 = requests.get(f"http://localhost:{self.port_http1}/v1/status/")
    +        status2 = requests.get(f"http://localhost:{self.port_http2}/v1/status/")
             assert status1.status_code == 200
             assert status2.status_code == 200
             assert "IRRD version" in status1.text
    @@ -594,8 +594,14 @@ def check_http(self):
             assert "Authoritative: Yes" in status1.text
             assert "Authoritative: Yes" not in status2.text
     
    +        # GHSA-22m3-c7vp-49fj CVE-2026-28681
    +        wrong_host = requests.get(
    +            f"http://localhost:{self.port_http1}/v1/status/", headers={"Host": "attacker.com"}
    +        )
    +        assert wrong_host.status_code == 400
    +
         def check_graphql(self):
    -        client = GraphqlClient(endpoint=f"http://127.0.0.1:{self.port_http1}/graphql/")
    +        client = GraphqlClient(endpoint=f"http://localhost:{self.port_http1}/graphql/")
             # Regular rpslObjects query including journal and several references
             query = """query {
               rpslObjects(rpslPk: "PERSON-TEST") {
    @@ -858,8 +864,6 @@ def _start_irrds(self):
                         "http": {
                             "status_access_list": "localhost",
                             "interface": "::1",
    -                        "port": 8080,
    -                        "url": "https://localhost:8080/",
                         },
                         "whois": {"interface": "::1", "max_connections": 10, "port": 8043},
                     },
    @@ -907,6 +911,7 @@ def _start_irrds(self):
             config1["irrd"]["redis_url"] = self.redis_url1
             config1["irrd"]["server"]["http"]["interface"] = "127.0.0.1"  # #306
             config1["irrd"]["server"]["http"]["port"] = self.port_http1
    +        config1["irrd"]["server"]["http"]["url"] = f"https://localhost:{self.port_http1}/"
             config1["irrd"]["server"]["whois"]["interface"] = "127.0.0.1"
             config1["irrd"]["server"]["whois"]["port"] = self.port_whois1
             config1["irrd"]["auth"]["gnupg_keyring"] = str(self.tmpdir) + "/gnupg1"
    @@ -928,6 +933,7 @@ def _start_irrds(self):
             config2["irrd"]["database_url"] = self.database_url2
             config2["irrd"]["redis_url"] = self.redis_url2
             config2["irrd"]["server"]["http"]["port"] = self.port_http2
    +        config1["irrd"]["server"]["http"]["url"] = f"https://localhost:{self.port_http2}/"
             config2["irrd"]["server"]["whois"]["port"] = self.port_whois2
             config2["irrd"]["auth"]["gnupg_keyring"] = str(self.tmpdir) + "/gnupg2"
             config2["irrd"]["log"]["logfile_path"] = self.logfile2
    @@ -954,6 +960,7 @@ def _start_irrds(self):
             config3["irrd"]["database_url"] = self.database_url3
             config3["irrd"]["redis_url"] = self.redis_url3
             config3["irrd"]["server"]["http"]["port"] = self.port_http3
    +        config1["irrd"]["server"]["http"]["url"] = f"https://localhost:{self.port_http3}/"
             config3["irrd"]["server"]["whois"]["port"] = self.port_whois3
             config3["irrd"]["auth"]["gnupg_keyring"] = str(self.tmpdir) + "/gnupg3"
             config3["irrd"]["log"]["logfile_path"] = self.logfile3
    
  • irrd/server/http/app.py+4 0 modified
    @@ -3,6 +3,7 @@
     import os
     import signal
     from pathlib import Path
    +from urllib.parse import urlparse
     
     import limits
     from ariadne.asgi import GraphQL
    @@ -12,6 +13,7 @@
     from starlette.applications import Starlette
     from starlette.middleware import Middleware
     from starlette.middleware.sessions import SessionMiddleware
    +from starlette.middleware.trustedhost import TrustedHostMiddleware
     from starlette.responses import RedirectResponse
     from starlette.routing import Mount, Route, WebSocketRoute
     from starlette.staticfiles import StaticFiles
    @@ -148,13 +150,15 @@ def set_middleware(app):
         csrf_disabled = testing and not getattr(app, "force_csrf_in_testing", False)
         if csrf_disabled:
             logger.info("Running in testing mode, disabling CSRF.")
    +    allowed_host = urlparse(get_setting("server.http.url")).hostname
         app.user_middleware = [
             # Use asgi-log to work around https://github.com/encode/uvicorn/issues/1384
             Middleware(
                 AccessLoggerMiddleware,
                 logger=logger,
                 format='%(client_addr)s - "%(request_line)s" %(status_code)s - %(L)ss',
             ),
    +        Middleware(TrustedHostMiddleware, allowed_hosts=[allowed_host]),
             Middleware(MemoryTrimMiddleware),
             Middleware(SessionMiddleware, secret_key=secret_key_derive("web.session_middleware")),
             Middleware(
    
  • irrd/webui/auth/users.py+2 1 modified
    @@ -159,5 +159,6 @@ def validate_token(self, token: str) -> bool:
                 return False
     
         def _hash(self, expiry_days: int | str) -> bytes:
    -        hash_data = secret_key_derive("web.password_reset_token") + self.user_key + str(expiry_days)
    +        # https://github.com/irrdnet/irrd/security/advisories/GHSA-22m3-c7vp-49fj
    +        hash_data = secret_key_derive("web.password_reset_token2") + self.user_key + str(expiry_days)
             return hashlib.sha224(hash_data.encode("utf-8")).digest()
    
  • poetry.lock+54 2 modified
    @@ -365,7 +365,7 @@ version = "25.4.0"
     description = "Classes Without Boilerplate"
     optional = false
     python-versions = ">=3.9"
    -groups = ["dev"]
    +groups = ["main", "dev"]
     files = [
         {file = "attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373"},
         {file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"},
    @@ -2740,6 +2740,33 @@ files = [
         {file = "py_radix_sr-1.0.2.tar.gz", hash = "sha256:f3727d46a5dda4b5d159d8da0f12a2b23b21120e92d8ed30c192727016d919fd"},
     ]
     
    +[[package]]
    +name = "pyasn1"
    +version = "0.6.2"
    +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
    +optional = false
    +python-versions = ">=3.8"
    +groups = ["main"]
    +files = [
    +    {file = "pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf"},
    +    {file = "pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b"},
    +]
    +
    +[[package]]
    +name = "pyasn1-modules"
    +version = "0.4.2"
    +description = "A collection of ASN.1-based protocols modules"
    +optional = false
    +python-versions = ">=3.8"
    +groups = ["main"]
    +files = [
    +    {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"},
    +    {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"},
    +]
    +
    +[package.dependencies]
    +pyasn1 = ">=0.6.1,<0.7.0"
    +
     [[package]]
     name = "pycparser"
     version = "3.0"
    @@ -3408,6 +3435,31 @@ files = [
         {file = "ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b"},
     ]
     
    +[[package]]
    +name = "service-identity"
    +version = "24.2.0"
    +description = "Service identity verification for pyOpenSSL & cryptography."
    +optional = false
    +python-versions = ">=3.8"
    +groups = ["main"]
    +files = [
    +    {file = "service_identity-24.2.0-py3-none-any.whl", hash = "sha256:6b047fbd8a84fd0bb0d55ebce4031e400562b9196e1e0d3e0fe2b8a59f6d4a85"},
    +    {file = "service_identity-24.2.0.tar.gz", hash = "sha256:b8683ba13f0d39c6cd5d625d2c5f65421d6d707b013b375c355751557cbe8e09"},
    +]
    +
    +[package.dependencies]
    +attrs = ">=19.1.0"
    +cryptography = "*"
    +pyasn1 = "*"
    +pyasn1-modules = "*"
    +
    +[package.extras]
    +dev = ["coverage[toml] (>=5.0.2)", "idna", "mypy", "pyopenssl", "pytest", "types-pyopenssl"]
    +docs = ["furo", "myst-parser", "pyopenssl", "sphinx", "sphinx-notfound-page"]
    +idna = ["idna"]
    +mypy = ["idna", "mypy", "types-pyopenssl"]
    +tests = ["coverage[toml] (>=5.0.2)", "pytest"]
    +
     [[package]]
     name = "setproctitle"
     version = "1.3.7"
    @@ -4904,4 +4956,4 @@ files = [
     [metadata]
     lock-version = "2.1"
     python-versions = ">3.10.0,<4.0"
    -content-hash = "0a4f6b5c571ab1345e6b2dd5e346823db1fb1b20320903ddda660d002c79d865"
    +content-hash = "f270f379f6f1d7f81bdc5302a35c99fc4325985b285d5c8799c7462e764d7c66"
    
  • pyproject.toml+1 0 modified
    @@ -65,6 +65,7 @@ email-validator = "2.3.0"
     asgi-logger = "0.1.0"
     joserfc = "1.6.3"
     time-machine = "3.2.0"
    +service-identity = "^24.2.0"
     
     [tool.poetry.group.dev.dependencies]
     pytest = "^7.2"
    

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

7

News mentions

0

No linked articles in our index yet.