BREW-KIMI-CLI-CVE-2026-116672 (GHSA-C73Q-W75P-J82V)

Vulnerability from osv_homebrew – Published: 2026-10-31 11:02 – Updated: 2026-10-31 11:02 – Source website
VLAI
Summary
Authlib RFC 7592 examples omit registration-access-token client binding
Details

Description

Summary

RFC 7592 (OAuth 2.0 Dynamic Client Registration Management Protocol) lets a previously registered client read, update, or delete its own registration, authenticated by a registration_access_token issued specifically to that client at registration time. The entire security model of this protocol depends on the server verifying that the token presented in the request actually belongs to the client identified in the request, not merely that the token is valid for some client.

Authlib's ClientConfigurationEndpoint implements the control flow correctly in structure, calling authenticate_token, then authenticate_client, then check_permission, and treats all three as abstract methods that "Developers MUST implement in subclass." The problem is that the documented example implementations for these methods, taken together exactly as written, never perform the one check that matters: binding the authenticated token to the specific client being accessed. A developer who implements these methods by following the provided examples ends up with a complete authorization bypass on this endpoint.

Details

authenticate_client's documented example (authlib/oauth2/rfc7592/endpoint.py):

def authenticate_client(self, request):
    """Read a client from the request payload.
    Developers MUST implement this method in subclass::

        def authenticate_client(self, request):
            client_id = request.payload.data.get("client_id")
            return Client.get(client_id=client_id)

    :return: client instance
    """
    raise NotImplementedError()

This looks the target client up purely by the client_id supplied in the request payload, with no reference to the token at all.

check_permission's documented example, in the same file:

def check_permission(self, client, request):
    """Checks whether the current client is allowed to be accessed, edited
    or deleted. Developers MUST implement it in subclass, e.g.::

        def check_permission(self, client, request):
            return client.editable

    :return: boolean
    """
    raise NotImplementedError()

This checks a property of the target client alone (whether it is generally "editable"), again with no reference to request.credential, the authenticated token object returned earlier by authenticate_token and stored on the request specifically so that later steps can check it:

def create_configuration_response(self, request):
    token = self.authenticate_token(request)
    if not token:
        raise AccessDeniedError()

    request.credential = token

    client = self.authenticate_client(request)
    ...
    if not self.check_permission(client, request):
        raise UnauthorizedClientError(...)

request.credential is set specifically so that a binding check can be performed downstream, but neither documented example ever reads it. Following both examples as written produces an implementation where the only requirements to access, modify, or delete a client's registration are: possessing any valid registration_access_token for any client, and knowing or guessing the target client_id.

This is meaningfully different from how the equivalent example is written for the token introspection endpoint (RFC 7662) elsewhere in the same codebase, which does correctly demonstrate checking the authenticated caller's own identity (authlib/oauth2/rfc7662/introspection.py):

def check_permission(self, token, client, request):
    """Check if the request has permission to introspect the token. Developers
    MUST implement this method::

        def check_permission(self, token, client, request):
            # only allow a special client to introspect the token
            return client.client_id == "introspection_client"
    """

In that endpoint, client represents the already-authenticated caller (verified through normal client credential authentication earlier in the flow), so checking a property of client is meaningful authorization logic. In the RFC 7592 endpoint, client instead represents the target being acted upon, not the caller, so the same style of example is actively misleading: it looks like a reasonable authorization check but performs none of the binding the protocol requires.

No file anywhere in the repository, including the Flask, Django, and SQLAlchemy integration modules, provides a complete, corrected reference implementation of ClientConfigurationEndpoint. The misleading docstring examples are the only guidance shipped for this endpoint.

Realistic exploitability

RFC 7591 dynamic client registration is commonly deployed without requiring an initial access token, since the specification explicitly anticipates open, self-service registration as a normal deployment model. Under that common configuration, an attacker can freely self-register their own throwaway client with no authentication at all, receive a valid registration_access_token scoped to that throwaway client, and then present that same token against /register/{target_client_id} for any other client, since neither documented example checks that the token corresponds to the target. If the target client's editable property (or whatever equivalent the developer implements based on the documented example) is true, an attacker can read the victim client's full registration including its metadata, update it (for example, changing redirect_uris to an attacker-controlled endpoint, enabling authorization code or token interception against that client's users), or delete it outright.

Reproduction

  1. Deploy an authorization server using ClientConfigurationEndpoint with authenticate_client and check_permission implemented exactly as documented in the class's own docstrings.
  2. As an attacker, register a new client via the RFC 7591 registration endpoint (commonly open/anonymous), obtaining client_id_A and registration_access_token_A.
  3. Identify or guess client_id_B, belonging to a different, unrelated client.
  4. Send GET /register/client_id_B (or PUT/DELETE) with Authorization: Bearer registration_access_token_A, and client_id set to client_id_B in the request body for the PUT case.
  5. authenticate_token succeeds because registration_access_token_A is a valid token. authenticate_client succeeds because it looks up client_id_B directly from the payload, ignoring which token was presented. check_permission succeeds because it only checks client_B.editable, not whether registration_access_token_A has any relationship to client_B. The request proceeds and returns, modifies, or deletes client B's registration.

Impact

Full read, modify, and delete access to any other client's registration record, for any party able to obtain a valid registration_access_token for any client of their own. In an open dynamic registration deployment this reduces to no meaningful barrier at all, since attackers can self-issue their own qualifying token for free. Impact includes disclosure of client metadata, redirect URI hijacking enabling authorization code or token interception, and denial of service via deletion of arbitrary client registrations.

Suggested fix

Update the documented example for check_permission (or authenticate_client) to explicitly demonstrate binding the authenticated token to the target client, for example:

def check_permission(self, client, request):
    return request.credential.client_id == client.get_client_id()

and add an explicit warning in both docstrings that omitting this comparison results in a complete authorization bypass on this endpoint. Consider also shipping a complete, correct reference implementation of ClientConfigurationEndpoint in the Flask/Django/SQLAlchemy integration modules, since none currently exists.


Affected products

Ecosystem: pip
Package name: Authlib
Affected versions: 1.7.2
Patched versions: 1.9.1

Severity

Privileges Required is Low, reflecting that the attacker needs some registration_access_token of their own, which under the common open dynamic registration deployment model is trivially self-issued. This is a documentation/reference-implementation issue rather than a bug the library enforces unconditionally on every deployment, since authenticate_client and check_permission are abstract methods a developer must implement; the finding is that the only guidance provided for implementing them correctly is, itself, exploitable if followed as written.


{
  "affected": [
    {
      "ecosystem_specific": {
        "fix": null,
        "range_state": "affected",
        "resource": "authlib",
        "resource_purl": "pkg:pypi/authlib@1.8.0",
        "upstream_fixed_in": "1.9.1"
      },
      "package": {
        "ecosystem": "Homebrew",
        "name": "kimi-cli",
        "purl": "pkg:brew/kimi-cli"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.40"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "database_specific": {
    "confidence": "high",
    "source": "matched",
    "strategy": "registry",
    "upstream_evidence": [
      {
        "ecosystem": "PyPI",
        "key": "pkg:pypi/authlib@1.8.0",
        "name": "authlib",
        "resource": "authlib",
        "strategy": "registry",
        "subject_version": "1.8.0"
      }
    ]
  },
  "details": "## Description\n\n### Summary\n\nRFC 7592 (OAuth 2.0 Dynamic Client Registration Management Protocol) lets a previously registered client read, update, or delete its own registration, authenticated by a `registration_access_token` issued specifically to that client at registration time. The entire security model of this protocol depends on the server verifying that the token presented in the request actually belongs to the client identified in the request, not merely that the token is valid for some client.\n\nAuthlib\u0027s `ClientConfigurationEndpoint` implements the control flow correctly in structure, calling `authenticate_token`, then `authenticate_client`, then `check_permission`, and treats all three as abstract methods that \"Developers MUST implement in subclass.\" The problem is that the documented example implementations for these methods, taken together exactly as written, never perform the one check that matters: binding the authenticated token to the specific client being accessed. A developer who implements these methods by following the provided examples ends up with a complete authorization bypass on this endpoint.\n\n### Details\n\n`authenticate_client`\u0027s documented example (`authlib/oauth2/rfc7592/endpoint.py`):\n\n```python\ndef authenticate_client(self, request):\n    \"\"\"Read a client from the request payload.\n    Developers MUST implement this method in subclass::\n\n        def authenticate_client(self, request):\n            client_id = request.payload.data.get(\"client_id\")\n            return Client.get(client_id=client_id)\n\n    :return: client instance\n    \"\"\"\n    raise NotImplementedError()\n```\n\nThis looks the target client up purely by the `client_id` supplied in the request payload, with no reference to the token at all.\n\n`check_permission`\u0027s documented example, in the same file:\n\n```python\ndef check_permission(self, client, request):\n    \"\"\"Checks whether the current client is allowed to be accessed, edited\n    or deleted. Developers MUST implement it in subclass, e.g.::\n\n        def check_permission(self, client, request):\n            return client.editable\n\n    :return: boolean\n    \"\"\"\n    raise NotImplementedError()\n```\n\nThis checks a property of the target client alone (whether it is generally \"editable\"), again with no reference to `request.credential`, the authenticated token object returned earlier by `authenticate_token` and stored on the request specifically so that later steps can check it:\n\n```python\ndef create_configuration_response(self, request):\n    token = self.authenticate_token(request)\n    if not token:\n        raise AccessDeniedError()\n\n    request.credential = token\n\n    client = self.authenticate_client(request)\n    ...\n    if not self.check_permission(client, request):\n        raise UnauthorizedClientError(...)\n```\n\n`request.credential` is set specifically so that a binding check can be performed downstream, but neither documented example ever reads it. Following both examples as written produces an implementation where the only requirements to access, modify, or delete a client\u0027s registration are: possessing any valid registration_access_token for any client, and knowing or guessing the target `client_id`.\n\nThis is meaningfully different from how the equivalent example is written for the token introspection endpoint (RFC 7662) elsewhere in the same codebase, which does correctly demonstrate checking the authenticated caller\u0027s own identity (`authlib/oauth2/rfc7662/introspection.py`):\n\n```python\ndef check_permission(self, token, client, request):\n    \"\"\"Check if the request has permission to introspect the token. Developers\n    MUST implement this method::\n\n        def check_permission(self, token, client, request):\n            # only allow a special client to introspect the token\n            return client.client_id == \"introspection_client\"\n    \"\"\"\n```\n\nIn that endpoint, `client` represents the already-authenticated caller (verified through normal client credential authentication earlier in the flow), so checking a property of `client` is meaningful authorization logic. In the RFC 7592 endpoint, `client` instead represents the target being acted upon, not the caller, so the same style of example is actively misleading: it looks like a reasonable authorization check but performs none of the binding the protocol requires.\n\nNo file anywhere in the repository, including the Flask, Django, and SQLAlchemy integration modules, provides a complete, corrected reference implementation of `ClientConfigurationEndpoint`. The misleading docstring examples are the only guidance shipped for this endpoint.\n\n### Realistic exploitability\n\nRFC 7591 dynamic client registration is commonly deployed without requiring an initial access token, since the specification explicitly anticipates open, self-service registration as a normal deployment model. Under that common configuration, an attacker can freely self-register their own throwaway client with no authentication at all, receive a valid `registration_access_token` scoped to that throwaway client, and then present that same token against `/register/{target_client_id}` for any other client, since neither documented example checks that the token corresponds to the target. If the target client\u0027s `editable` property (or whatever equivalent the developer implements based on the documented example) is true, an attacker can read the victim client\u0027s full registration including its metadata, update it (for example, changing `redirect_uris` to an attacker-controlled endpoint, enabling authorization code or token interception against that client\u0027s users), or delete it outright.\n\n### Reproduction\n\n1. Deploy an authorization server using `ClientConfigurationEndpoint` with `authenticate_client` and `check_permission` implemented exactly as documented in the class\u0027s own docstrings.\n2. As an attacker, register a new client via the RFC 7591 registration endpoint (commonly open/anonymous), obtaining `client_id_A` and `registration_access_token_A`.\n3. Identify or guess `client_id_B`, belonging to a different, unrelated client.\n4. Send `GET /register/client_id_B` (or `PUT`/`DELETE`) with `Authorization: Bearer registration_access_token_A`, and `client_id` set to `client_id_B` in the request body for the `PUT` case.\n5. `authenticate_token` succeeds because `registration_access_token_A` is a valid token. `authenticate_client` succeeds because it looks up `client_id_B` directly from the payload, ignoring which token was presented. `check_permission` succeeds because it only checks `client_B.editable`, not whether `registration_access_token_A` has any relationship to `client_B`. The request proceeds and returns, modifies, or deletes client B\u0027s registration.\n\n### Impact\n\nFull read, modify, and delete access to any other client\u0027s registration record, for any party able to obtain a valid registration_access_token for any client of their own. In an open dynamic registration deployment this reduces to no meaningful barrier at all, since attackers can self-issue their own qualifying token for free. Impact includes disclosure of client metadata, redirect URI hijacking enabling authorization code or token interception, and denial of service via deletion of arbitrary client registrations.\n\n## Suggested fix\n\nUpdate the documented example for `check_permission` (or `authenticate_client`) to explicitly demonstrate binding the authenticated token to the target client, for example:\n\n```python\ndef check_permission(self, client, request):\n    return request.credential.client_id == client.get_client_id()\n```\n\nand add an explicit warning in both docstrings that omitting this comparison results in a complete authorization bypass on this endpoint. Consider also shipping a complete, correct reference implementation of `ClientConfigurationEndpoint` in the Flask/Django/SQLAlchemy integration modules, since none currently exists.\n\n---\n\n## Affected products\n\n**Ecosystem:** pip  \n**Package name:** Authlib  \n**Affected versions:** 1.7.2  \n**Patched versions:** 1.9.1\n\n## Severity\n\nPrivileges Required is Low, reflecting that the attacker needs some registration_access_token of their own, which under the common open dynamic registration deployment model is trivially self-issued. This is a documentation/reference-implementation issue rather than a bug the library enforces unconditionally on every deployment, since `authenticate_client` and `check_permission` are abstract methods a developer must implement; the finding is that the only guidance provided for implementing them correctly is, itself, exploitable if followed as written.",
  "id": "BREW-kimi-cli-CVE-2026-116672",
  "modified": "2026-10-31T11:02:09Z",
  "published": "2026-10-31T11:02:09Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/authlib/authlib/security/advisories/GHSA-c73q-w75p-j82v"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-116672"
    },
    {
      "type": "WEB",
      "url": "https://github.com/authlib/authlib/commit/1bd2a313492ce0a4d0571c150730907aa3fc91ff"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/authlib/authlib"
    },
    {
      "type": "WEB",
      "url": "https://github.com/authlib/authlib/releases/tag/v1.9.1"
    }
  ],
  "schema_version": "1.7.3",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Authlib RFC 7592 examples omit registration-access-token client binding",
  "upstream": [
    "GHSA-c73q-w75p-j82v",
    "CVE-2026-116672"
  ]
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    Loading…

    Loading…

    Related by attack behaviour

    Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.