GHSA-6RMH-7XCM-CPXJ
Vulnerability from github – Published: 2026-05-11 13:56 – Updated: 2026-05-11 13:56Summary
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](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:15).check_auth()returnsTruewhenever authentication is disabled, so both protected routes fail open by design at [src/praisonai/api_server.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:18).POST /chatonly checks that the request JSON contains amessagekey and then runsPraisonAI(agent_file="agents.yaml").run()at [src/praisonai/api_server.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:31).GET /agentsis guarded by the same no-op authentication check and returns agent metadata at [src/praisonai/api_server.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:55).- 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](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/praisonai/deploy/models.py:23).- The generated sample API deployment YAML recommends
host: 0.0.0.0together withauth_enabled: falsein [src/praisonai/praisonai/deploy/schema.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/praisonai/deploy/schema.py:108).
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](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/praisonai/cli/commands/serve.py:155). 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.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.6.33"
},
"package": {
"ecosystem": "PyPI",
"name": "PraisonAI"
},
"ranges": [
{
"events": [
{
"introduced": "2.5.6"
},
{
"fixed": "4.6.34"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-44338"
],
"database_specific": {
"cwe_ids": [
"CWE-1188",
"CWE-306",
"CWE-668"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-11T13:56:16Z",
"nvd_published_at": "2026-05-08T14:16:46Z",
"severity": "HIGH"
},
"details": "### Summary\nPraisonAI 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.\n\n### Details\nThe vulnerable server is the shipped `src/praisonai/api_server.py` entrypoint.\n\n- `AUTH_ENABLED = False` and `AUTH_TOKEN = None` are hard-coded at [[src/praisonai/api_server.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:15)](/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:15).\n- `check_auth()` returns `True` whenever authentication is disabled, so both protected routes fail open by design at [[src/praisonai/api_server.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:18)](/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:18).\n- `POST /chat` only checks that the request JSON contains a `message` key and then runs `PraisonAI(agent_file=\"agents.yaml\").run()` at [[src/praisonai/api_server.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:31)](/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:31).\n- `GET /agents` is guarded by the same no-op authentication check and returns agent metadata at [[src/praisonai/api_server.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:55)](/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/[src/praisonai/api_server.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:66):55).\n- When launched directly, the same script binds to `0.0.0.0:8080` at [src/praisonai/api_server.py](/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/api_server.py:66).\n\nThe deploy subsystem keeps the same insecure authentication default:\n\n- `APIConfig` defaults `auth_enabled` to `False` in [[src/praisonai/praisonai/deploy/models.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/praisonai/deploy/models.py:23)](/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/praisonai/deploy/models.py:23).\n- The generated sample API deployment YAML recommends `host: 0.0.0.0` together with `auth_enabled: false` in [[src/praisonai/praisonai/deploy/schema.py](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/praisonai/deploy/schema.py:108)](/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/praisonai/deploy/schema.py:108).\n\nFor 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](https://github.com/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/praisonai/cli/commands/serve.py:155)](/Users/shmulc/Stuff/tmp/first-cve/scans/variant-hunt/PraisonAI/src/praisonai/praisonai/cli/commands/serve.py:155). This report is about the shipped legacy API server and the generated/sample API deployment path above.\n\nVersion scope:\n\n- `v2.5.6` already ships the same `src/praisonai/api_server.py` implementation.\n- The current PyPI release on May 1, 2026 is `4.6.33`, and it still ships the same unauthenticated server logic.\n\n### PoC\nThe following route-level reproduction was verified locally and proves that the shipped `api_server.py` exposes `/agents` and `/chat` without authentication.\n\n1. From the repository root, create a throwaway environment with the server\u0027s direct Flask dependencies:\n\n```bash\npython3 -m venv /tmp/praisonai-ghsa-venv\n/tmp/praisonai-ghsa-venv/bin/pip install flask flask-cors\n```\n\n2. Execute the shipped `src/praisonai/api_server.py` under a minimal stub for `praisonai.PraisonAI` so only the server auth logic is exercised:\n\n```bash\n/tmp/praisonai-ghsa-venv/bin/python - \u003c\u003c\u0027PY\u0027\nimport importlib.util\nimport pathlib\nimport sys\nimport types\n\nstub = types.ModuleType(\"praisonai\")\n\nclass DummyPraisonAI:\n def __init__(self, agent_file=\"agents.yaml\"):\n self.agent_file = agent_file\n def run(self):\n return {\"ran\": True, \"agent_file\": self.agent_file}\n\nstub.PraisonAI = DummyPraisonAI\nsys.modules[\"praisonai\"] = stub\n\npath = pathlib.Path(\"src/praisonai/api_server.py\").resolve()\nspec = importlib.util.spec_from_file_location(\"api_server_local\", path)\nmod = importlib.util.module_from_spec(spec)\nspec.loader.exec_module(mod)\n\nclient = mod.app.test_client()\nprint(client.get(\"/agents\").status_code, client.get(\"/agents\").get_data(as_text=True))\nprint(client.post(\"/chat\", json={\"message\": \"hello\"}).status_code, client.post(\"/chat\", json={\"message\": \"hello\"}).get_data(as_text=True))\nPY\n```\n\n3. Observed result:\n\n```text\n200 {\"agent_file\":\"agents.yaml\",\"agents\":[\"default\"]}\n200 {\"response\":{\"agent_file\":\"agents.yaml\",\"ran\":true},\"status\":\"success\"}\n```\n\nBoth endpoints succeed without any `Authorization` header.\n\n### Impact\nAny reachable caller can invoke the legacy API server\u0027s protected functionality without a token.\n\nAt minimum, this allows:\n\n- unauthenticated enumeration of the configured agent file through `/agents`\n- unauthenticated triggering of the locally configured `agents.yaml` workflow through `/chat`\n- repeated consumption of model/API quota and any other side effects performed by that workflow\n- exposure of whatever result `PraisonAI.run()` returns to the unauthenticated caller\n\nThis 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\u0027s `agents.yaml` is allowed to do, but the authentication bypass is unconditional in the shipped legacy server.",
"id": "GHSA-6rmh-7xcm-cpxj",
"modified": "2026-05-11T13:56:16Z",
"published": "2026-05-11T13:56:16Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-6rmh-7xcm-cpxj"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-44338"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L",
"type": "CVSS_V3"
}
],
"summary": "PraisonAI ships and generates a legacy API server with authentication disabled by default, allowing unauthenticated workflow execution"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.