CVE-2026-60091High· 7.2▾ TwilightPraisonAI: Jobs API is unauthenticated by default and allows attacker-controlled webhook SSRF
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 39.6 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.3%
PraisonAI's Async Jobs API enables its API-key middleware only when PRAISONAI_JOBS_API_KEY is set, so by default every endpoint is unauthenticated. An unauthenticated POST /api/v1/runs accepts an attacker-controlled webhook_url; on job completion the server POSTs the job payload to it via httpx. The webhook_url has an SSRF validator (gethostbyname + private-IP check), but it validates at request time while httpx re-resolves at connection time — a DNS-rebinding TOCTOU that reaches internal services. Runtime-confirmed as an unauthenticated, blind SSRF (the internal canary received the POST; the internal response is not returned to the attacker). Severity Medium.
praisonai 4.6.63. Files: src/praisonai/praisonai/jobs/server.py, jobs/router.py, jobs/executor.py, jobs/models.py.Path:
src/praisonai/praisonai/jobs/server.py
Function:
create_app
Snippet:
jobs_api_key = os.environ.get("PRAISONAI_JOBS_API_KEY")
# ...
if jobs_api_key:
app.add_middleware(JobsAPIKeyMiddleware) # auth ONLY when env var is set
Issue: with the env var unset (default), no auth middleware is added → all endpoints unauthenticated. Default bind is 127.0.0.1.
Path:
src/praisonai/praisonai/jobs/router.py
Function:
submit_job
Snippet:
@router.post("", response_model=JobSubmitResponse, status_code=202)
async def submit_job(request, response, body: JobSubmitRequest, ...):
# no auth dependency; body.webhook_url is attacker-controlled
job = Job(prompt=body.prompt, webhook_url=body.webhook_url, ...)
await executor.submit(job)
Issue: attacker-controlled webhook_url flows into the job with no authentication on the endpoint.
Path:
src/praisonai/praisonai/jobs/models.py (validator) and src/praisonai/praisonai/jobs/executor.py (sink)
Function:
validate_webhook_url → _send_webhook
Snippet:
# models.py validate_webhook_url (CHECK time)
ip = socket.gethostbyname(hostname)
if ipaddress.ip_address(ip).is_private or ...:
raise ValueError("Webhook URL resolves to a private or restricted network address")
# executor.py _send_webhook (CONNECT time, re-resolves, no pinning)
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(job.webhook_url, json=payload, ...)
Issue: the validator resolves the hostname at validation time but does not pin the IP for the httpx.post connection → DNS rebinding (independent resolution at check vs connect) bypasses it and reaches internal services.
PRAISONAI_JOBS_API_KEY (default → unauth).POST /api/v1/runs with webhook_url = a rebinding domain.httpx.post re-resolves to an internal IP and connects → SSRF to internal.Auth is opt-in (only when the env var is set). The webhook_url validator resolves at check time but does not pin the IP for the connection → DNS-rebinding TOCTOU. (Correction to an earlier static note: the guard exists but is bypassable.)
Unauthenticated network peer → server-side request to internal services (Scope: Changed). Default bind 127.0.0.1 limits remote reach unless the operator binds non-loopback.
Real Jobs API (python -m praisonai.jobs.server, no API key) in a local runtime (127.0.0.1:18085), resolver pointed at the controlled rebinding DNS, internal canary Docker-internal only. Runnable assets: PraisonAI-Runtime-Repro\runtime-files\ (docker-compose.jobs.yml).
PRAI-04-01-Jobs-Submit-NoAuth: POST /api/v1/runs {"prompt":"hello"} → 202 (no auth).PRAI-04-02-Jobs-Webhook-SSRF-Blind: POST /api/v1/runs {"prompt":"hello","webhook_url":"http://rebind.lab:8081/secret"} → 202.Unauthenticated job submission should be rejected; the webhook SSRF guard should prevent reaching internal services regardless of DNS timing.
Unauthenticated job submission (LLM cost abuse); SSRF to internal services (blind, DNS-rebinding); exfiltration of the job result to an attacker-controlled webhook.
webhook_url: resolve once, reject private/loopback/CGNAT/metadata, then pin and connect to the validated IP; disable redirects.praisonai <= 4.6.77Upgrade to a patched release:
praisonai 4.6.78Connected by shared product, vendor, weakness, or advisory.
GHSA-3wrm-pm5v-8vq8Medium· 7.2Duplicate Advisory: PraisonAI: Jobs API is unauthenticated by default and allows attacker-controlled webhook SSRF
CVE-2026-55537High· 7.1PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114
CVE-2026-55535Medium· 6.8PraisonAI vulnerable to Server-Side Request Forgery via DNS rebinding bypass in webhook_url validation
GHSA-rjvw-7vvw-549vHigh· 7.2PraisonAI: Jobs webhook SSRF protection bypass via DNS rebinding
GHSA-892r-p3jq-jp24Critical· 9.8PraisonAI: AgentOS remains unauthenticated after incomplete fix version and allows remote agent invocation
GHSA-jxcw-qp4h-6jfqHigh· 7.5PraisonAI A2U incomplete authentication fix leaves current serve command unauthenticated by default