CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-114620

CVE-2026-114620: Server-Side Request Forgery in Authlib RFC 9101 JWT-Secured Authorization Request

Amit Schendel
Amit Schendel
Senior Security Researcher

Oct 27, 2026·11 min read·1 visit

Executive Summary (TL;DR)

Authlib < 1.9.0 passes unauthenticated `request_uri` parameters to `get_request_object()` without prior validation in its RFC 9101 implementation. If downstream applications implement this method using unrestricted HTTP fetches as shown in legacy documentation, attackers with a valid `client_id` can perform blind SSRF and internal network reconnaissance via error response differentiation.

Authlib versions prior to 1.9.0 contain a Server-Side Request Forgery (SSRF) vulnerability in the RFC 9101 JWT-Secured Authorization Request (JAR) server implementation. The JWTAuthenticationRequest class passes an unauthenticated, client-controlled request_uri parameter directly to the abstract get_request_object() method before applying any destination validation or client registration policies. Because the official documentation previously recommended implementing this hook as an unrestricted HTTP fetch, deployments following those guidelines allow remote attackers possessing a valid public client_id to trigger outbound network requests to internal services and infer fetch outcomes via error response variations.

Vulnerability Overview

Authlib is a Python library used to construct OAuth 1.0, OAuth 2.0, and OpenID Connect (OIDC) clients and servers, alongside cryptographic JOSE specifications. Among its supported extensions is RFC 9101, which defines the OAuth 2.0 JWT-Secured Authorization Request (JAR) framework. RFC 9101 allows OAuth clients to pass authorization request parameters either by value (request) or by reference (request_uri) pointing to a remotely hosted, signed, and optionally encrypted JSON Web Token (JWT).

In versions of Authlib prior to 1.9.0, the server-side handling of the request_uri parameter in authlib/oauth2/rfc9101/authorization_server.py exhibits a Server-Side Request Forgery (SSRF) design flaw tracked as CVE-2026-114620 (GHSA-x4rv-c3wx-4cr8) and classified under CWE-918. During the processing of an unauthenticated OAuth authorization request (/oauth/authorize), the JWTAuthenticationRequest extension extracts the request_uri parameter from the query string and passes it directly to the developer-implemented get_request_object() method without validating the URI against pre-registered client metadata or enforcing a destination policy.

While get_request_object() is an abstract hook that performs no network operations in the base library, Authlib's official documentation and docstrings historically instructed developers to implement this method using an unrestricted outbound HTTP call (requests.get(request_uri).text). In deployments that adopted this documented pattern without adding custom validation layers, an unauthenticated remote attacker who knows a valid public client_id can coerce the authorization server into issuing HTTP requests to arbitrary internal, loopback, or link-local destinations.

Furthermore, the subsequent processing of the retrieved payload creates a reliable side-channel oracle. By observing whether the authorization endpoint returns an invalid_request_uri error versus an invalid_request_object error, along with measuring response timing, an attacker can differentiate between unreachable internal hosts and active HTTP services returning non-JWT content.

Root Cause Analysis

The root cause of CVE-2026-114620 lies in the execution order and missing validation primitives within the JWTAuthenticationRequest class in authlib/oauth2/rfc9101/authorization_server.py. Under RFC 9101 Section 3.2.2 and Section 10.4.1, authorization servers resolving a request_uri must mitigate SSRF risks by requiring clients to pre-register valid URIs (via the request_uris client metadata parameter defined in RFC 7591) or by enforcing strict outbound fetch policies prior to initiating network connections.

In Authlib < 1.9.0, the request parsing pipeline is invoked via parse_authorization_request(self, request: OAuth2Request, client: ClientMixin). Once the extension confirms via _shoud_proceed_with_request_object() that the request contains a JAR parameter and that the server supports request_uri, it calls self._get_raw_request_object(request). Crucially, _get_raw_request_object() did not accept the resolved client instance as a parameter, making it architecturally impossible for the method to verify whether the incoming request_uri belonged to the client's registered allowlist prior to retrieval.

Because this execution occurs at the initial stage of the authorization request, no client authentication (such as verifying a client_secret or private_key_jwt) has taken place. The server merely resolves the ClientMixin database record corresponding to the public client_id query parameter. Consequently, the untrusted request_uri string is passed straight into self.get_request_object(request_uri).

Compound with the missing validation step in the framework lifecycle, Authlib's documentation provided an insecure reference implementation for get_request_object(). The documented snippet called requests.get(request_uri).text inside a generic exception handler returning None on failure. This guidance lacked scheme verification (allowing http:// or potentially other handlers depending on the HTTP library), failed to block private/loopback IP ranges, allowed automatic HTTP redirects, enforced no response size limits, and did not verify the application/oauth-authz-req+jwt content type.

Finally, the error handling architecture inadvertently constructs a boolean SSRF oracle. If the outbound connection in get_request_object() fails (e.g., connection refused or timeout), the method returns None or an empty string, causing _get_raw_request_object() to raise InvalidRequestUriError (error=invalid_request_uri). Conversely, if the connection succeeds and retrieves arbitrary content (such as an internal HTML page or AWS IMDS metadata), _get_raw_request_object() passes that string to _decode_request_object(). When JWT parsing fails on the non-JWT response body, Authlib raises InvalidRequestObjectError (error=invalid_request_object). This distinct error code confirms to the attacker that an HTTP service responded at the target URI.

Code Analysis

Before version 1.9.0, authlib/oauth2/rfc9101/authorization_server.py handled request_uri extraction without passing client context or invoking any policy verification hook. In addition, the inline docstring explicitly demonstrated an unvalidated requests.get() call.

# Vulnerable code in authlib/oauth2/rfc9101/authorization_server.py (< 1.9.0)
class JWTAuthenticationRequest(AuthorizationEndpointMixin):
    def parse_authorization_request(self, request: OAuth2Request, client: ClientMixin):
        if not self._shoud_proceed_with_request_object(request, client):
            return
 
        # Client context is dropped here; no validation performed before fetch
        raw_request_object = self._get_raw_request_object(request)
        request_object = self._decode_request_object(
            request, client, raw_request_object
        )
        # ...
 
    def _get_raw_request_object(self, request: OAuth2Request) -> str:
        if "request_uri" in request.payload.data:
            # Unchecked user input passed directly to the retrieval hook
            raw_request_object = self.get_request_object(
                request.payload.data["request_uri"]
            )
            if not raw_request_object:
                raise InvalidRequestUriError(state=request.payload.state)
            return raw_request_object
        return request.payload.data["request"]
 
    def get_request_object(self, request_uri: str):
        """Download the request object from request_uri.
        ...
        class JWTAuthenticationRequest(rfc9101.JWTAuthenticationRequest):
            def get_request_object(self, request_uri: str):
                try:
                    return requests.get(request_uri).text
                except requests.Exception:
                    return None
        """
        raise NotImplementedError()

The remediation, implemented across commits 6563f6f7a8c2f71d2f85ee7511d72a4fbae3acc0 and 891e3f10ae5989176211f8be35dcc7cd40aa92c6 (PR #949), restructures the validation workflow to enforce a fail-closed default policy and decouples OIDC-specific dynamic registration checks into a dedicated subclass.

# Patched code in authlib/oauth2/rfc9101/authorization_server.py (1.9.0)
class JWTAuthenticationRequest(AuthorizationEndpointMixin):
    def parse_authorization_request(self, request: OAuth2Request, client: ClientMixin):
        if not self._shoud_proceed_with_request_object(request, client):
            return
 
        # Pass the resolved client object into the extraction method
        raw_request_object = self._get_raw_request_object(request, client)
        request_object = self._decode_request_object(
            request, client, raw_request_object
        )
        # ...
 
    def _get_raw_request_object(
        self, request: OAuth2Request, client: ClientMixin
    ) -> str:
        if "request_uri" in request.payload.data:
            request_uri = request.payload.data["request_uri"]
            # Enforce non-empty URI and explicit policy validation before calling get_request_object
            if not request_uri or not self.validate_request_uri(request_uri, client):
                raise InvalidRequestUriError(state=request.payload.state)
            raw_request_object = self.get_request_object(request_uri)
            if not raw_request_object:
                raise InvalidRequestUriError(state=request.payload.state)
            return raw_request_object
 
        return request.payload.data["request"]
 
    def validate_request_uri(self, request_uri: str, client: ClientMixin) -> bool:
        """Return whether request_uri may be retrieved for this client.
 
        Called before get_request_object. The default rejects every URI;
        applications supporting request_uri must implement a secure policy.
        """
        return False

To support OpenID Connect Core 1.0 deployments that rely on the require_request_uri_registration server metadata parameter and per-client request_uris allowlists, commit 891e3f10ae5989176211f8be35dcc7cd40aa92c6 introduced OpenIDJWTAuthenticationRequest in authlib/oidc/core/:

# Added in authlib/oidc/core/ (1.9.0)
class OpenIDJWTAuthenticationRequest(JWTAuthenticationRequest):
    def _get_raw_request_object(
        self, request: OAuth2Request, client: ClientMixin
    ) -> str:
        if "request_uri" in request.payload.data:
            metadata = self.get_server_metadata()
            if metadata.get("require_request_uri_registration", False):
                request_uri = request.payload.data["request_uri"]
                if request_uri not in self.get_client_request_uris(client):
                    raise InvalidRequestUriError(state=request.payload.state)
        return super()._get_raw_request_object(request, client)

> [!NOTE] > Assessment of Fix Completeness: The patch effectively neutralizes the framework-level bypass by introducing validate_request_uri() with a default return value of False. Upgrading to 1.9.0 immediately breaks existing request_uri flows unless the developer explicitly overrides validate_request_uri(). However, because Authlib still delegates the actual HTTP transport to the downstream developer in get_request_object(), applications remain vulnerable to DNS rebinding or HTTP redirect SSRF if the developer overrides validate_request_uri() with naive string prefix checks rather than exact registered URI matching and hardened network transport controls.

Exploitation

Exploiting CVE-2026-114620 requires three preconditions: the target OAuth 2.0 / OIDC authorization server must run Authlib < 1.9.0 with JWTAuthenticationRequest enabled (support_request_uri=True), the integrator must have implemented get_request_object() using an unrestricted HTTP client (such as Python's requests or httpx), and the attacker must know a valid client_id registered on the server. Because client_id values are exposed in public login URLs, mobile applications, and single-page applications, obtaining one is trivial.

To execute the attack, the adversary sends crafted HTTP GET requests to the authorization endpoint (typically /oauth/authorize) with the target internal resource specified in the request_uri parameter:

GET /oauth/authorize?response_type=code&client_id=public-web-app&request_uri=http://169.254.169.254/latest/meta-data/iam/security-credentials/&state=xyz HTTP/1.1
Host: idp.example.com

When the server processes this request, it looks up public-web-app, verifies that the client exists, and passes http://169.254.169.254/latest/meta-data/iam/security-credentials/ directly to get_request_object(). The server then initiates an outbound TCP connection to the link-local cloud metadata address.

By analyzing the OAuth error response (returned either directly in the HTTP response body or as query parameters in a 302 Found redirect to the client's redirect_uri), the attacker can map the internal network:

  1. Closed Port / Unreachable Host: If http://10.0.0.5:8080/ is closed or times out, requests.get() raises a ConnectionError or Timeout, which the documented try...except block catches to return None. Authlib responds with error=invalid_request_uri.
  2. Open Port / Non-JWT HTTP Response: If http://10.0.0.5:8080/ hosts an active HTTP server that returns any non-empty body (even a 404 or 500 page, since requests.get().text does not check status codes without .raise_for_status()), get_request_object() returns the response string. Authlib passes this string to _decode_request_object(), which fails to parse it as a compact JWS/JWE and responds with error=invalid_request_object.

If the underlying HTTP client in get_request_object() supports additional URI schemes (for example, if implemented using urllib.request.urlopen() instead of requests), the attacker can also probe local file existence via file:///etc/passwd, where readable non-empty files return invalid_request_object and non-existent files return invalid_request_uri.

Impact Assessment

The severity of CVE-2026-114620 is rated Medium with a CVSS v3.1 base score of 5.4 (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N). The Attack Complexity is scored as High (AC:H) because Authlib does not perform network requests by itself; vulnerability realization depends on the integrating application implementing get_request_object() without its own destination filtering. The Scope is Changed (S:C) because the vulnerable OAuth authorization server acts as a deputy to reach backend systems and internal network segments isolated from the public internet.

In affected deployments, the primary confidentiality impact (C:L) stems from internal network reconnaissance and port scanning via the differential error oracle (invalid_request_uri vs. invalid_request_object) and response timing analysis. Because the raw response body from get_request_object() is consumed internally by the JWT decoder and discarded upon parsing failure, this vulnerability functions as a blind SSRF rather than a full-read SSRF, unless verbose debug exception traces are exposed by the web framework.

Despite being blind with respect to raw HTTP response bodies, the integrity impact (I:L) allows attackers to trigger state-changing HTTP GET requests against internal unauthenticated APIs, management interfaces, or legacy microservices that perform actions via query parameters. Additionally, if an attacker controls an external server and the client registration allows arbitrary URIs or the validation is weak, an attacker could host a valid signed JWT (if using a known or none-equivalent misconfiguration, though Authlib enforces JWS verification) or abuse slow-loris style responses to tie up worker threads.

Current threat intelligence indicates an EPSS score of 0.0029 (21.46th percentile), reflecting a low probability of mass automated exploitation due to the conditional nature of custom RFC 9101 integrations. The vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog.

Remediation

To remediate CVE-2026-114620, upgrade authlib to version 1.9.0 or later. Version 1.9.0 introduces the validate_request_uri(self, request_uri: str, client: ClientMixin) -> bool method on JWTAuthenticationRequest, which defaults to returning False and is evaluated prior to get_request_object().

After upgrading to 1.9.0, applications that require request_uri support must explicitly override validate_request_uri() to enforce an allowlist policy. For OpenID Connect servers, switch to or subclass OpenIDJWTAuthenticationRequest from authlib.oidc.core and enable require_request_uri_registration: True in the server metadata so that incoming request_uri values are strictly matched against pre-registered URIs returned by get_client_request_uris(client).

# Hardened implementation pattern for Authlib >= 1.9.0
from urllib.parse import urlparse
import httpx
from authlib.oauth2.rfc9101 import JWTAuthenticationRequest
 
class HardenedJARRequest(JWTAuthenticationRequest):
    def validate_request_uri(self, request_uri: str, client) -> bool:
        # 1. Require exact match against pre-registered client request_uris
        allowed_uris = client.get_registered_request_uris()
        if not allowed_uris or request_uri not in allowed_uris:
            return False
        
        # 2. Enforce HTTPS scheme
        parsed = urlparse(request_uri)
        return parsed.scheme == "https" and bool(parsed.netloc)
 
    def get_request_object(self, request_uri: str):
        # 3. Disable redirects, set strict timeouts, and enforce Content-Type
        try:
            with httpx.Client(follow_redirects=False, timeout=3.0) as http:
                resp = http.get(
                    request_uri,
                    headers={"Accept": "application/oauth-authz-req+jwt"}
                )
                resp.raise_for_status()
                if len(resp.content) > 262144:  # 256 KB limit
                    return None
                return resp.text
        except httpx.HTTPError:
            return None

If an immediate upgrade to 1.9.0 is not feasible, administrators can mitigate the vulnerability in two ways. First, if remote reference resolution is not required, disable request_uri support entirely by passing support_request_uri=False when initializing JWTAuthenticationRequest (clients can still pass JWT request objects by value via the request parameter). Second, if request_uri must remain enabled on an older version, implement strict network-level filtering directly inside get_request_object() to resolve the target hostname, block private/loopback/link-local IP ranges (RFC 1918, RFC 3927, IPv6 Unique Local/Link-Local), disable HTTP redirects, and enforce HTTPS.

Official Patches

AuthlibOfficial GitHub Security Advisory for GHSA-x4rv-c3wx-4cr8 / CVE-2026-114620
AuthlibPull Request #949 addressing unvalidated request_uri fetch
AuthlibAuthlib v1.9.0 Release Notes

Fix Analysis (2)

Technical Appendix

CVSS Score
5.4/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N
EPSS Probability
0.29%
Top 79% most exploited

Affected Systems

Authlib (Python package)

Affected Versions Detail

Product
Affected Versions
Fixed Version
authlib
Authlib
< 1.9.01.9.0
AttributeDetail
Vulnerability TypeServer-Side Request Forgery (CWE-918)
Affected Componentauthlib.oauth2.rfc9101.JWTAuthenticationRequest
Attack VectorNetwork (Unauthenticated HTTP GET to OAuth Authorization Endpoint)
CVSS v3.1 Score5.4 Medium (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:L/I:L/A:N)
EPSS Score0.29% (21.46th Percentile)
ImpactBlind SSRF, Internal Port/Service Scanning via Error Oracle
Exploit StatusProof-of-Concept (Unit Test Suite)
Patched Versionauthlib 1.9.0

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1005Data from Local System
Collection
CWE-918
Server-Side Request Forgery (SSRF)

The web application receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

Vulnerability Timeline

Initial fix commit (6563f6f7a8c2f71d2f85ee7511d72a4fbae3acc0) authored to enforce request_uri validation prior to fetch.
2026-10-08
Refactor commit (891e3f10ae5989176211f8be35dcc7cd40aa92c6) separating OIDC registration logic merged via Pull Request #949.
2026-10-12
CVE-2026-114620 identifier reserved.
2026-10-21
Authlib 1.9.0 released; GitHub Security Advisory GHSA-x4rv-c3wx-4cr8 and CVE-2026-114620 published.
2026-10-22
NVD completes vulnerability analysis and scoring.
2026-10-23

References & Sources

  • [1]GitHub Security Advisory GHSA-x4rv-c3wx-4cr8
  • [2]NVD - CVE-2026-114620 Detail
  • [3]CVE.org Record: CVE-2026-114620
  • [4]Authlib Pull Request #949
  • [5]Fix Commit 6563f6f7a8c2f71d2f85ee7511d72a4fbae3acc0
  • [6]Fix Commit 891e3f10ae5989176211f8be35dcc7cd40aa92c6
  • [7]Authlib Release v1.9.0
  • [8]CVE List V5 JSON Record

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•24 minutes ago•CVE-2026-110816
5.3

CVE-2026-110816: Denial of Service via Uncaught Exception in Node.js qs Library

CVE-2026-110816 (GHSA-6pq8-jhm2-2vcw) is an uncaught exception and improper type conversion vulnerability in the Node.js query string parsing and serialization library qs. When invoking qs.stringify with the option arrayFormat set to 'comma', the library fails to safely handle array elements that cannot be coerced into primitive strings, such as objects with a non-callable toString property or null-prototype objects. An unauthenticated remote attacker can supply crafted input via JSON bodies or URL query parameters that, when re-serialized by the application, throws a synchronous TypeError, resulting in HTTP 500 errors or complete Node.js process termination.

Alon Barad
Alon Barad
4 views•10 min read
•about 2 hours ago•GHSA-J456-XH4H-CPF2
7.0

CVE-2026-109956: Path Traversal and Sandbox Bypass in NLTK WekaClassifier

CVE-2026-109956 (GHSA-j456-xh4h-cpf2) is a path traversal vulnerability and security sandbox bypass in the Python Natural Language Toolkit (NLTK) versions 3.10.3 and earlier. The WekaClassifier module fails to validate caller-controlled model file paths before passing them to the Weka Java Virtual Machine (JVM) via command-line arguments. When operating under the nltk.pathsec security sandbox, this omission permits attackers who control model path parameters to read, probe, or overwrite arbitrary files outside authorized NLTK data directories.

Alon Barad
Alon Barad
5 views•10 min read
•about 3 hours ago•CVE-2026-35002
9.8

CVE-2026-35002: Eval Injection and Arbitrary Code Execution in Agno Agent Platform

CVE-2026-35002 is an arbitrary code execution vulnerability in the Agno agent platform (formerly Phidata) affecting all versions prior to 2.3.24. The flaw resides in the model execution logic (libs/agno/agno/models/base.py) and tool serialization logic (libs/agno/agno/tools/function.py), where the application directly passes the field_type string parameter into Python's built-in eval() function to resolve string type names into Python data types. An attacker who can influence FunctionCall arguments via prompt injection or supply untrusted dictionary structures for deserialization can execute arbitrary Python expressions within the context of the host process.

Amit Schendel
Amit Schendel
3 views•9 min read
•3 days ago•CVE-2026-115895
8.8

CVE-2026-115895: Code Injection via Unsanitized Import Alias in Happy DOM ECMAScript Module Compiler

CVE-2026-115895 is a high-severity code injection vulnerability (CWE-94) in the happy-dom npm package prior to version 21.0.4. The vulnerability exists within the ECMAScriptModuleCompiler component, which transforms ECMAScript module syntax into executable Node.js functions when JavaScript evaluation is enabled. Due to an incomplete fix for CVE-2026-33943, the compiler failed to validate local named import aliases against legal JavaScript identifier rules. Attackers supplying crafted module imports—specifically utilizing destructuring default initializers alongside JSON module attributes—can inject arbitrary JavaScript expressions directly into the host Node.js runtime context.

Amit Schendel
Amit Schendel
11 views•10 min read
•3 days ago•CVE-2026-115883
6.0

CVE-2026-115883: Cross-Namespace Access via Dot-Delimited Labels in LangGraph Postgres and SQLite Stores

An improper input validation and authorization bypass vulnerability exists in the LangGraph storage layer affecting langgraph, langgraph-checkpoint-postgres, and langgraph-checkpoint-sqlite. Because relational store implementations serialize namespace tuples into dot-delimited strings without validating labels on read, search, delete, and batch operations, an attacker who controls a namespace label can inject periods (.) to traverse namespace boundaries and access, modify, or delete data belonging to other namespaces.

Amit Schendel
Amit Schendel
9 views•9 min read
•3 days ago•CVE-2026-115886
6.5

CVE-2026-115886: Unauthenticated Server-Side Request Forgery and Unsafe Reflection in MLflow Scorer Evaluation Endpoints

CVE-2026-115886 (GHSA-2wqp-q3c6-ccj5) is a Server-Side Request Forgery (SSRF) and Unsafe Reflection vulnerability in MLflow versions 3.12.0rc0 prior to 3.17.0. Unauthenticated REST API endpoints accept serialized third-party scorer JSON payloads that are dynamically deserialized and reflectively instantiated without sanitizing constructor keyword arguments. When optional dependencies such as trulens and trulens-providers-litellm are installed, attackers can inject egress-steering arguments into the scorer payload, forcing the MLflow server to dispatch outbound HTTP requests to arbitrary internal destinations or cloud metadata services.

Alon Barad
Alon Barad
13 views•10 min read