GitHub Advisoryadvisory · PyPI
GHSA-fq2m-6wqh-x44g PraisonAI: Jobs API exposes agent-execution endpoints with no authentication
CRITICAL
Official page ↗
- Aliases
- CVE-2026-57131
- Ecosystem
- PyPI
- Packages
- praisonai
- Severity
- Critical
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Weaknesses
- CWE-306, CWE-862, CWE-94
- Reviewed
- 2026-06-18
Affected packages
• PyPI praisonai: < 4.6.59 → fixed in 4.6.59
Description
praisonai: Jobs API exposes agent-execution endpoints with no authentication
Researcher: Kai Aizen — SnailSploit (@SnailSploit), Adversarial & Offensive Security Research
Target: https://github.com/MervinPraison/PraisonAI
---
Package: `praisonai` on PyPI
Affected version (empirically tested): 4.6.48
Components:
• `praisonai.jobs.server.create_app` — `praisonai/jobs/server.py`
• `praisonai.jobs.router.create_router` — `praisonai/jobs/router.py`
• Routes mounted at `/api/v1/runs/...`
Weakness: CWE-306 Missing Authentication for Critical Function · CWE-862 Missing Authorization · CWE-94 Code Injection (via prompt / agent_yaml).
---
TL;DR
`praisonai` ships a standalone async-jobs HTTP server (`python -m praisonai.jobs.server --host=0.0.0.0 --port=8005`) whose job is to accept job submissions and run agents on the operator's behalf. Every endpoint under `/api/v1/runs` is unauthenticated. There is no `auth_token` field, no `Depends(verify_*)`, no middleware that inspects `Authorization` — the CORS middleware *lists* `Authorization` in `allow_headers` (the only signal in the whole module that the developer was aware authentication is a thing), but no route ever reads it.
A network-reachable attacker can:
1. Execute arbitrary agent code — `POST /api/v1/runs` accepts `prompt`, `agent_yaml`, `agent_file`, `config`, `framework`. The job is queued and an executor invokes whichever framework (`praisonai` / `crewai` / `autogen`) the attacker picks, with whichever prompt and tool config the attacker supplies. The job runs in the operator's process — same environment variables, same filesystem, same credentials (OpenAI / Anthropic / Azure / Bedrock keys; tool integrations; on-disk YAML recipes).
2. List and read every job system-wide — `GET /api/v1/runs` lists all jobs; `GET /api/v1/runs/{job_id}/result` returns the full result of any completed job. Operator's prompts, the agent's chain-of-thought, tool inputs / outputs, retrieved documents — all readable to an anonymous client.
3. Cancel or delete any job — `POST /…/cancel` and `DELETE /…/{job_id}` accept arbitrary job IDs without any ownership / authorization check.
4. Stream live SSE of any in-flight job — `GET /…/{job_id}/stream` reads the executor's live progress for any job ID.
The remote-RCE shape (1) is the load-bearing one. Even with `webhook_url` SSRF-guarded (and it is — the model validator at `jobs/models.py:42-65` rejects localhost / private IPs), the attacker needs no callback: SSE streaming returns the agent's output directly on the same connection.
Root cause
```
Expected behavior when starting `praisonai.jobs.server`:
"I'm running an HTTP API my application backend will call.
The CORS middleware permits Authorization, so the server
enforces it. Anonymous attackers cannot submit jobs."
Actual behavior (praisonai 4.6.48):
• server.py:59-152 create_app builds a FastAPI app, adds
CORSMiddleware, includes the jobs router.
NO auth middleware. NO global Depends.
• router.py:43 @router.post("") submit_job(...)
No Depends, no Authorization header read,
no auth_token config field at all.
• router.py:109,148,161,180,205,224 every other route:
likewise, no auth on any of GET-list,
GET-status, GET-result, POST-cancel,
DELETE, GET-stream.
• server.py:117 CORS allow_headers DOES include
"Authorization" — the only token in the
entire jobs/ subpackage that suggests
the developer was thinking about auth.
Impact:
The API is intended to be production-ready (the CORS code at
server.py:96-102 explicitly branches on
`os.getenv("ENVIRONMENT") == "production"` to harden origins),
yet ships with no authentication layer at all. Operators who
bind the server to a network interface — including the
suggested `--host=0.0.0.0` in the CLI parser — expose
unauthenticated agent execution to anyone who can reach the
port.
```
The same package gets auth right elsewhere (`praisonai/gateway/server.py` auto-generates an `auth_token` if none is configured and refuses to serve requests without it; `praisonai/endpoints/a2u_server.py:250-264` uses `hmac.compare_digest` on a Bearer token). The jobs API is the outlier.
Empirically affected routes
Verified by PoC against published `praisonai==4.6.48` (`/api/v1/runs/...` paths):
| Method | Path | Unauth result |
|----------|-------------------------------|--------------------------|
| `POST` | `/api/v1/runs` | HTTP 202 Accepted, attacker job queued and executor invoked the framework |
| `GET` | `/api/v1/runs` | HTTP 200, lists every job in the store |
| `GET` | `/api/v1/runs/{job_id}` | HTTP 200, returns status of any job |
| `GET` | `/api/v1/runs/{job_id}/result`| (untested; same router, no auth) |
| `POST` | `/api/v1/runs/{job_id}/cancel`| HTTP 200 / 409 (processed) |
| `DELETE` | `/api/v1/runs/{job_id}` | HTTP 204 No Content (deleted) |
| `GET` | `/api/v1/runs/{job_id}/stream`| (untested; SSE; same router, no auth) |
PoC run log excerpt (`poc/run-log.txt`):
```
[1] POST /api/v1/runs (no Authorization) -> HTTP 202
body: {"job_id":"run_90f21c98b82a","status":"queued",...}
[01:15:44] executor.py:201 ERROR Job failed: run_90f21c98b82a -
OPENAI_API_KEY environment variable is required ...
```
The executor's error confirms the prompt reached the framework's LLM-invocation step. Had the operator set `OPENAI_API_KEY`, the attacker prompt would have executed.
Impact details
1. Remote code execution via agent invocation
`JobSubmitRequest.framework` accepts `"praisonai"`, `"crewai"`, or `"autogen"`. Each framework can be configured (via the YAML / config the attacker sends) to use arbitrary tools. praisonai's tool loaders (`praisonai/agents_generator.py` `load_tools_from_module*`) have a documented history of arbitrary-import (CVE-2026-40287 and its fix-of-fix CVE-2026-44334). In practice the operator's installation may or may not expose these sinks; either way the attacker controls the prompt, which the LLM will execute with whatever tools the operator wired (including shell, filesystem, browser, …).
The job executor runs in-process under the operator's service account, with full access to environment variables (LLM API keys, tool tokens) and to anything `praisonai`'s tools normally touch.
2. Cross-tenant data read
A single-process deployment uses an `InMemoryJobStore` that is flat — no `user_id` / `tenant_id` / `workspace_id` partition. Any client that knows or guesses a job ID can read it. Worse, the list endpoint (`GET /api/v1/runs`) returns every job, so guessing isn't even necessary.
Sensitive content in the result includes the attacker's input (harmless) but also any *legitimate* user's input that the operator's backend submitted — and the agent's full output, which may contain data the agent retrieved from the operator's databases or APIs.
3. Denial of service via job deletion / cancellation
`DELETE` and `cancel` accept any job ID. An attacker who polls the list endpoint can enumerate IDs and cancel-then-delete every job in flight, breaking the operator's backend's polling-for-completion flow.
4. webhook_url SSRF — defended
To the developer's credit, `JobSubmitRequest.webhook_url` is validated against localhost / private / link-local / multicast IPs at submission time (`jobs/models.py:42-65`). This blocks the naive "submit a job whose webhook posts to AWS IMDS" attack. Honest yield: this is properly guarded.
Anchors
praisonai 4.6.48, source file `praisonai/jobs/server.py` (sha256 `10b5deab96686f276b8ad71fa4712e1e3d301e4…
Related
Alias · CVE (1)
- CVE-2026-57131 PraisonAI is a multi-agent teams system. vulnerability