VAITP Dataset

← Back to the dataset

CVE-2026-54635

pytonapi webhook authentication is bypassed when using a custom path.

  • CVSS 7.5
  • 287
  • Authentication, Authorization, and Session Management
  • Remote

pytonapi is a Python SDK for TONAPI that provides REST API, streaming, and webhook access to the TON blockchain. From 2.0.0 to 2.2.0, TonapiWebhookDispatcher fails to validate the Authorization header when a webhook handler is registered with the documented path argument, because setup() stores bearer tokens only under the default suffix paths and never adds the custom path to the token map, so self._tokens.get(path) returns None and the authentication guard is skipped. An unauthenticated remote attacker can POST forged payloads to the custom webhook endpoint and trigger victim-defined handlers. This issue is fixed in version 2.2.1.

CWE
287
CVSS base score
7.5
Published
2026-07-28
OWASP
A07 Identification and Authentication Failures
Orthogonal defect classification
Checking
Code defect classification
Missing Check
Category
Authentication, Authorization, and Session Management
Subcategory
Insecure Authentication Mechanisms
Accessibility scope
Remote
Impact
Unauthorized Access
Affected component
pytonapi
Fixed by upgrading
Yes

Solution

Upgrade `pytonapi` to version 2.2.1 or later.

Vulnerable code sample

from typing import Dict, Optional

# Mock objects for context
from aiohttp import web

class BaseHandler: ...
class MempoolHandler(BaseHandler): ...
class TransactionHandler(BaseHandler): ...
class TONAPI:
    def __init__(self, api_key: str):
        self.api_key = api_key

class TonapiWebhookDispatcher:
    def __init__(self, tonapi: TONAPI):
        self.tonapi = tonapi
        self._handlers: Dict[str, BaseHandler] = {}
        self._tokens: Dict[str, str] = {}

    def setup(self, handler: BaseHandler, path: Optional[str] = None) -> None:
        token = self.tonapi.api_key
        if isinstance(handler, MempoolHandler):
            webhook_path = "/v1/mempool/webhook"
            self._handlers[webhook_path] = handler
            self._tokens[webhook_path] = token
        elif isinstance(handler, TransactionHandler):
            webhook_path = "/v1/transaction/webhook"
            self._handlers[webhook_path] = handler
            self._tokens[webhook_path] = token
        elif path:
            # VULNERABLE: The custom path is used to register the handler, but the token is not stored.
            self._handlers[path] = handler

    async def _handle_request(self, request: web.Request) -> web.Response:
        token = self._tokens.get(request.path)
        if token:  # This authentication check is skipped for custom paths
            auth_header = request.headers.get("Authorization")
            if not auth_header or auth_header != f"Bearer {token}":
                raise web.HTTPUnauthorized()

        handler = self._handlers.get(request.path)
        if handler:
            return web.Response(text="OK")
        return web.Response(status=404)

Patched code sample

from typing import Dict, Optional

# Mock objects for context
from aiohttp import web

class BaseHandler: ...
class MempoolHandler(BaseHandler): ...
class TransactionHandler(BaseHandler): ...
class TONAPI:
    def __init__(self, api_key: str):
        self.api_key = api_key

class TonapiWebhookDispatcher:
    def __init__(self, tonapi: TONAPI):
        self.tonapi = tonapi
        self._handlers: Dict[str, BaseHandler] = {}
        self._tokens: Dict[str, str] = {}

    def setup(self, handler: BaseHandler, path: Optional[str] = None) -> None:
        token = self.tonapi.api_key
        if isinstance(handler, MempoolHandler):
            path = "/v1/mempool/webhook"
        elif isinstance(handler, TransactionHandler):
            path = "/v1/transaction/webhook"

        if path:
            self._handlers[path] = handler
            # FIX: The token is now stored for both default and custom paths.
            self._tokens[path] = token

    async def _handle_request(self, request: web.Request) -> web.Response:
        token = self._tokens.get(request.path)
        if token:
            auth_header = request.headers.get("Authorization")
            if not auth_header or auth_header != f"Bearer {token}":
                raise web.HTTPUnauthorized()

        handler = self._handlers.get(request.path)
        if handler:
            return web.Response(text="OK")
        return web.Response(status=404)

Payload

{
  "events": [
    {
      "event_id": "a1b2c3d4e5f67890fakedata",
      "timestamp": 1678886400,
      "actions": [
        {
          "type": "JettonTransfer",
          "status": "ok",
          "JettonTransfer": {
            "sender": {
              "address": "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"
            },
            "recipient": {
              "address": "UQAvs2-vJp1W1A3oP9vP5gZ_ZJ-3Z-J-3Z-J-3Z-J-3Z-J-3Z"
            },
            "amount": "1000000000",
            "jetton_master": {
              "address": "EQB-ajMyi5-WKi5-WKi5-WKi5-WKi5-WKi5-WKi5-WKi5-WKi"
            }
          }
        }
      ]
    }
  ]
}

Cite this entry

@misc{vaitp:cve202654635,
  title        = {{pytonapi webhook authentication is bypassed when using a custom path.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-54635},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-54635/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::