Severity by source
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
Network-accessible with no authentication or interaction required; C/I/A held at Low reflecting base agent invocation impact without assuming specific high-privilege tool configurations.
Primary rating from Vendor (https://github.com/MervinPraison/PraisonAI).
CVSS VectorVendor: https://github.com/MervinPraison/PraisonAI
Lifecycle Timeline
3DescriptionCVE.org
Summary
praisonai serve agents exposes HTTP routes that invoke registered agents. The CLI advertises --api-key with help text "API key for authentication", parses it, and forwards it into ServeHandler. But _create_agents_app() never reads config["api_key"] again and installs no auth dependency or middleware on its direct routes. The configured key is a no-op flag.
As a result, an unauthenticated network caller can invoke exposed agents (POST /agents and POST /agents/{agent_name}) even when the operator passed --api-key. Requests with no credentials, a wrong Authorization: Bearer, a wrong X-API-Key, or an empty bearer all reach agent.start().
The failure is made sharper by the fact that a working auth dependency already exists in the same module - praisonai.api.agent_invoke.verify_token guards every /api/v1/... route with Depends(verify_token) and is mounted into the very same app. The direct n8n-compat routes simply do not use it.
Technical Detail
Source-to-sink trace
1. CLI advertises and forwards --api-key:
# cli/commands/serve.py
@app.command("agents")
def serve_agents(..., api_key: Optional[str] = typer.Option(None, "--api-key", help="API key for authentication")):
...
if api_key:
args.extend(["--api-key", api_key])
exit_code = handle_serve_command(args)2. cmd_agents() parses api_key into the spec - and that is the last time it is touched:
# cli/features/serve.py - cmd_agents()
spec = { ..., "api_key": {"default": None} }
parsed = self._parse_args(args, spec)
app = self._create_agents_app(parsed)A grep of the entire cli/features/serve.py for api_key returns only the two spec entries (cmd_agents line ~199 and cmd_unified line ~847). config["api_key"] is never read inside _create_agents_app() / _create_unified_app(); it is never compared, and no dependency is attached.
3. _create_agents_app() imports FastAPI, HTTPException, Request - no Depends, no Header, no auth middleware. Every HTTPException raised in the agents routes is 400/404/500 (validation / not-found / execution error); none is 401.
4. Sink - unauthenticated request reaches agent.start():
# cli/features/serve.py
@app.post("/agents/{agent_name}")
# n8n compatibility route
async def invoke_single_agent(agent_name: str, request: Request):
body = await request.json()
query = body.get("query", "") or body.get("message", "")
...
agent = agent_invoke.get_agent(agent_name)
result = await loop.run_in_executor(None, agent.start, query)
# no auth anywhere above
return {"response": str(result)}
@app.post(path)
# default path "/agents"
async def invoke_agents(request: Request, query_data: AgentQuery = None):
... agent.start(query) ...The auth dependency exists - it just isn't applied here
_create_agents_app() mounts the agent_invoke router into the same app:
# cli/features/serve.py
if getattr(agent_invoke, 'FASTAPI_AVAILABLE', False) and hasattr(agent_invoke, 'router'):
app.include_router(agent_invoke.router)That router properly authenticates every sensitive route:
# api/agent_invoke.py
CALL_SERVER_TOKEN = os.getenv('CALL_SERVER_TOKEN')
async def verify_token(request, authorization=Header(None)) -> None:
...
if token != CALL_SERVER_TOKEN:
raise HTTPException(status_code=401, detail="Unauthorized")
@router.get("/api/v1/agents")
async def list_agents(_: None = Depends(verify_token)): ...
# and register/unregister/info all use itSo in the same process GET /api/v1/agents returns 401 without a token, while POST /agents/{agent_name} returns 200. Note also that verify_token reads the CALL_SERVER_TOKEN env var - not the CLI --api-key - so the CLI option feeds no auth path at all.
Trigger conditions
praisonai serve agents --file agents.yaml --host 0.0.0.0 --port 8765 --api-key expected-secret
POST /agents/{agent_name} body {"query":"..."} with no / wrong / empty credentialsProof of Concept
Built the real _create_agents_app() and exercised it over HTTP via FastAPI TestClient. Only praisonaiagents.Agent is stubbed (.start() returns EXEC:<query>), so no real LLM/credentials. CALL_SERVER_TOKEN=expected-secret was set so the sibling /api/v1 router is genuinely armed - making the contrast explicit.
Operator started with: --api-key expected-secret (CALL_SERVER_TOKEN also set)
== Sibling /api/v1 route WITH Depends(verify_token) ==
GET /api/v1/agents [no creds ] -> HTTP 401
GET /api/v1/agents [wrong bearer] -> HTTP 401
== Direct agent-invocation route (the bug) ==
POST /agents/owned [no creds ] -> HTTP 200 {'response': 'EXEC:hello'}
POST /agents/owned [wrong bearer ] -> HTTP 200 {'response': 'EXEC:hello'}
POST /agents/owned [wrong x-api-key] -> HTTP 200 {'response': 'EXEC:hello'}
POST /agents/owned [empty bearer ] -> HTTP 200 {'response': 'EXEC:hello'}
POST /agents [no creds ] -> HTTP 200 {'response': 'EXEC:hi'}The auth mechanism works for /api/v1 (401) and is entirely absent on the direct /agents routes (200), despite --api-key being configured.
Equivalent HTTP trigger in a fully installed environment
praisonai serve agents --file agents.yaml --host 0.0.0.0 --port 8765 --api-key expected-secret
curl -sS -X POST http://TARGET:8765/agents/owned \
-H 'Content-Type: application/json' --data-binary '{"query":"hello"}'
# -> 200 {"response":"..."} (expected: 401 Unauthorized)Impact
- Direct primitive: unauthenticated agent invocation despite a configured API key.
- Misleading control (aggravating): because the CLI advertises
--api-keyas authentication, operators may deliberately expose the service (e.g.--host 0.0.0.0, reverse proxy, n8n integration) believing it is protected, increasing the real-world likelihood of exposure. - Downstream: exposed agents commonly hold LLM provider credentials, RAG/memory, browser/search, MCP, or shell/file tools; the bypass lets an attacker drive those capabilities. Baseline impact is unauthorized LLM cost + access to agent responses.
Suggested Mitigation
- When
config["api_key"]is set, build a shared auth dependency and attach it to every agent-invocation / state-changing route in_create_agents_app()and_create_unified_app()(dependencies=[Depends(verify)]). - Reuse / unify with the existing
verify_tokenso the direct/agentsroutes and the/api/v1routes share one mechanism, and wire the CLI--api-keyinto that mechanism (today it feeds nothing;verify_tokenreadsCALL_SERVER_TOKEN). - Use constant-time comparison (
hmac.compare_digest);verify_tokencurrently uses!=. - Update discovery metadata from
auth_modes=["none"]to["api-key","bearer"]for protected endpoints. - Regression tests next to
tests/unit/test_serve_unified.py:_create_agents_app({"api_key":"secret",...})→POST /agents/{name}with no creds / wrongAuthorization/ wrongX-API-Keyreturns401; correct key succeeds.
import hmac
from fastapi import Header, HTTPException, Depends
def _auth_dependency(expected_key: str):
async def verify(authorization: str | None = Header(None),
x_api_key: str | None = Header(None, alias="X-API-Key")):
token = x_api_key
if authorization and authorization.startswith("Bearer "):
token = authorization[7:]
if not token or not hmac.compare_digest(token, expected_key):
raise HTTPException(status_code=401, detail="Unauthorized")
return Depends(verify)AnalysisAI
Authentication bypass in PraisonAI's HTTP agent-serving layer allows unauthenticated remote invocation of registered agents despite the operator having configured an API key via --api-key. The root defect is in _create_agents_app(): it never reads config["api_key"] and never attaches a FastAPI Depends() auth dependency to POST /agents and POST /agents/{agent_name}, making the advertised --api-key flag a no-op. …
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 | The vulnerable service must be running pip/praisonai at a version earlier than 4.6.58 via the praisonai serve agents command. … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | The CVSS 7.3 base score (AV:N/AC:L/PR:N/UI:N) accurately captures the no-prerequisite, network-accessible nature of the bypass, and the published PoC demonstrating live HTTP 200 responses to credentialless requests substantially increases realistic exploitation probability beyond what the score alone conveys. … 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 to PraisonAI v4.6.58 or later by running pip install --upgrade praisonai; this is the vendor-released patch confirmed by release tag v4.6.58 at https://github.com/MervinPraison/PraisonAI/releases/tag/v4.6.58 and patch commit 2f9677abb2ea68eab864ee8b6a828fd0141612e1. … Detailed patch versions, workarounds, and compensating controls in full report. |
Recommended ActionAI
Within 24 hours, inventory PraisonAI instances and assess which are network-accessible. …
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
Same technique Authentication Bypass
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-65514
GHSA-r7v3-x45f-g7hp