PYSEC-2026-465
Vulnerability from pysec - Published: 2026-06-29 11:50 - Updated: 2026-07-01 20:23Summary
CVE-2026-44338 (GHSA-6rmh-7xcm-cpxj) documents that PraisonAI ships a code-generator (praisonai.deploy.api.generate_api_server_code) that emits a Flask API server with authentication disabled by default. Users who follow the documented quickstart (praisonai deploy --type api) get a server that:
- binds to
0.0.0.0per the recommended sample YAML - exposes
/chatand/agentsendpoints - runs
praisonai.run()on user-supplied JSON input — LLM orchestration with the API key materials present in the process environment - does not require any authentication
The PyPI wheel praisonai==4.6.33 (current @latest) still ships the generator with auth_enabled defaulting to False. The fix shape is opt-in via APIConfig(auth_enabled=True, auth_token=...).
Details
Anchor (file:line:symbol)
- Vulnerable artifact:
praisonai==4.6.33on PyPI. - Defaults:
praisonai/deploy/models.py:29—auth_enabled: bool = Field(default=False, ...);praisonai/deploy/models.py:30—auth_token: Optional[str] = Field(default=None, ...). - Generator:
praisonai/deploy/api.py:40—AUTH_ENABLED = {config.auth_enabled};api.py:41—AUTH_TOKEN = {repr(config.auth_token)};api.py:43-49—def check_auth(): if not AUTH_ENABLED: return True. - CLI entry: documented as
praisonai deploy --type api(vendor README); produces the generator output above with no flag required to suppress the warning, because no warning is emitted.
Vulnerable code (verbatim from installed wheel)
# praisonai/deploy/models.py (praisonai==4.6.33)
class APIConfig(BaseModel):
host: str = Field(default="127.0.0.1", description="Server host")
port: int = Field(default=8005, description="Server port")
cors_enabled: bool = Field(default=True, description="Enable CORS")
auth_enabled: bool = Field(default=False, description="Enable authentication") # line 29
auth_token: Optional[str] = Field(default=None, description="Authentication token") # line 30
# praisonai/deploy/api.py (praisonai==4.6.33)
code = f\'\'\'...
# Authentication
AUTH_ENABLED = {config.auth_enabled} # False by default
AUTH_TOKEN = {repr(config.auth_token)} # None by default
def check_auth():
if not AUTH_ENABLED:
return True # short-circuit, accept all
token = request.headers.get(\'Authorization\', \'\').replace(\'Bearer \', \'\')
return token == AUTH_TOKEN
...
\'\'\'
A default invocation of the deploy command emits a server whose check_auth() short-circuits to True and accepts unauthenticated /chat, /agents POSTs.
PoC
#!/usr/bin/env python3
"""
legend-c420 PoC - PraisonAI 4.6.33 generates Flask API server with auth
disabled by default. Class H sibling of CVE-2026-44338.
Phase 1: reflect on praisonai.deploy.models.APIConfig defaults.
Phase 2: call generate_api_server_code(default config) and assert the
emitted source contains AUTH_ENABLED = False and the
short-circuit return.
Phase 3: re-run with auth_enabled=True, auth_token='s3cret-bearer-value'
and confirm the emitted source flips to the secure shape.
Exit code 0 = PASS = vulnerable defaults confirmed.
"""
import sys, traceback
def phase1_dataclass_defaults():
print("PHASE 1 - praisonai.deploy.models.APIConfig default values")
from praisonai.deploy.models import APIConfig
cfg = APIConfig()
checks = [
("auth_enabled", cfg.auth_enabled, False),
("auth_token", cfg.auth_token, None),
]
for name, observed, expected in checks:
ok = observed == expected
mark = "VULNERABLE" if name in ("auth_enabled","auth_token") and ok else "ok"
print(f" {name:14s} = {observed!r:18s} (expected {expected!r}) [{mark}]")
assert ok
print(" >> APIConfig defaults reproduce the CVE-2026-44338 shape.")
def phase2_default_generator_emits_unauth():
print("PHASE 2 - generate_api_server_code(default config) emits unauth server")
from praisonai.deploy.models import APIConfig
from praisonai.deploy.api import generate_api_server_code
src = generate_api_server_code("agents.yaml", config=APIConfig())
for needle in ["AUTH_ENABLED = False","AUTH_TOKEN = None","if not AUTH_ENABLED:","return True"]:
assert needle in src, f"missing: {needle!r}"
print(f" [FOUND] {needle!r}")
print(" >> Default-config generator emits Flask server with check_auth() short-circuit.")
def phase3_fix_shape_available():
print("PHASE 3 - auth_enabled=True flips to secure shape")
from praisonai.deploy.models import APIConfig
from praisonai.deploy.api import generate_api_server_code
cfg = APIConfig(auth_enabled=True, auth_token="s3cret-bearer-value")
src = generate_api_server_code("agents.yaml", config=cfg)
assert "AUTH_ENABLED = True" in src
assert "AUTH_ENABLED = False" not in src
print(" >> Fix shape works when toggled. Class H confirmed: default is insecure.")
def main():
print("=" * 64)
print("legend-c420 PoC - PraisonAI default-config AUTH_ENABLED=False")
print("=" * 64)
try:
phase1_dataclass_defaults()
phase2_default_generator_emits_unauth()
phase3_fix_shape_available()
except Exception:
traceback.print_exc()
print("FAIL"); sys.exit(2)
print("PASS 3/3 phases. EXIT 0.")
sys.exit(0)
if __name__ == "__main__":
main()
PoC dependencies: praisonai==4.6.33 from PyPI. Tested on Python 3.11.
Run log verdict: PASS 3/3 phases. EXIT 0. — vulnerable-default shape confirmed. auth_enabled=False by default, check_auth() short-circuits to True, fix toggle exists but is opt-in.
Impact
An operator who runs the vendor-documented quickstart (pip install praisonai && praisonai deploy --type api) gets a network-reachable Flask server that invokes praisonai.run() on attacker-supplied JSON with the user's LLM API keys in the process environment. The attacker reaches arbitrary LLM-orchestration (including any tool-use the agents define, which in PraisonAI commonly includes python_repl, bash, file I/O, and HTTP calls), with the host's API-key credit billed to the operator.
- Belief: CVE-2026-44338 was filed and triaged.
- Reality:
praisonai==4.6.33is current@lateston PyPI (2026-05-16). The generator still defaults toauth_enabled=False. - Gap: The CVE acknowledges the fix shape exists. The fix is opt-in. The default-config consumer remains vulnerable.
Parent CVE: CVE-2026-44338 / GHSA-6rmh-7xcm-cpxj
| Name | purl | praisonai | pkg:pypi/praisonai |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "praisonai",
"purl": "pkg:pypi/praisonai"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.6.40"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"0.0.1",
"0.0.10",
"0.0.11",
"0.0.12",
"0.0.13",
"0.0.14",
"0.0.15",
"0.0.16",
"0.0.17",
"0.0.18",
"0.0.19",
"0.0.2",
"0.0.20",
"0.0.21",
"0.0.22",
"0.0.23",
"0.0.24",
"0.0.25",
"0.0.26",
"0.0.27",
"0.0.28",
"0.0.29",
"0.0.3",
"0.0.30",
"0.0.31",
"0.0.32",
"0.0.33",
"0.0.34",
"0.0.35",
"0.0.36",
"0.0.37",
"0.0.38",
"0.0.39",
"0.0.4",
"0.0.40",
"0.0.41",
"0.0.42",
"0.0.43",
"0.0.44",
"0.0.45",
"0.0.46",
"0.0.47",
"0.0.48",
"0.0.49",
"0.0.5",
"0.0.50",
"0.0.52",
"0.0.53",
"0.0.54",
"0.0.55",
"0.0.56",
"0.0.57",
"0.0.58",
"0.0.59",
"0.0.59rc11",
"0.0.59rc2",
"0.0.59rc3",
"0.0.59rc5",
"0.0.59rc6",
"0.0.59rc7",
"0.0.59rc8",
"0.0.59rc9",
"0.0.6",
"0.0.61",
"0.0.64",
"0.0.65",
"0.0.66",
"0.0.67",
"0.0.68",
"0.0.69",
"0.0.7",
"0.0.70",
"0.0.71",
"0.0.72",
"0.0.73",
"0.0.74",
"0.0.8",
"0.0.9",
"0.1.0",
"0.1.1",
"0.1.10",
"0.1.2",
"0.1.3",
"0.1.4",
"0.1.5",
"0.1.6",
"0.1.7",
"0.1.8",
"0.1.9",
"1.0.0",
"1.0.1",
"1.0.10",
"1.0.11",
"1.0.2",
"1.0.3",
"1.0.4",
"1.0.5",
"1.0.6",
"1.0.8",
"1.0.9",
"2.0.0",
"2.0.1",
"2.0.10",
"2.0.11",
"2.0.12",
"2.0.13",
"2.0.14",
"2.0.15",
"2.0.16",
"2.0.17",
"2.0.18",
"2.0.19",
"2.0.2",
"2.0.20",
"2.0.22",
"2.0.23",
"2.0.24",
"2.0.25",
"2.0.26",
"2.0.27",
"2.0.28",
"2.0.29",
"2.0.3",
"2.0.30",
"2.0.31",
"2.0.32",
"2.0.33",
"2.0.34",
"2.0.35",
"2.0.36",
"2.0.37",
"2.0.38",
"2.0.39",
"2.0.40",
"2.0.41",
"2.0.42",
"2.0.43",
"2.0.44",
"2.0.45",
"2.0.46",
"2.0.47",
"2.0.48",
"2.0.49",
"2.0.5",
"2.0.50",
"2.0.51",
"2.0.53",
"2.0.54",
"2.0.55",
"2.0.56",
"2.0.57",
"2.0.58",
"2.0.59",
"2.0.6",
"2.0.60",
"2.0.61",
"2.0.62",
"2.0.63",
"2.0.64",
"2.0.65",
"2.0.66",
"2.0.67",
"2.0.68",
"2.0.69",
"2.0.7",
"2.0.70",
"2.0.71",
"2.0.72",
"2.0.73",
"2.0.74",
"2.0.75",
"2.0.76",
"2.0.77",
"2.0.78",
"2.0.79",
"2.0.8",
"2.0.80",
"2.0.81",
"2.0.9",
"2.1.0",
"2.1.1",
"2.1.4",
"2.1.5",
"2.1.6",
"2.2.1",
"2.2.10",
"2.2.11",
"2.2.12",
"2.2.13",
"2.2.14",
"2.2.15",
"2.2.16",
"2.2.17",
"2.2.18",
"2.2.19",
"2.2.2",
"2.2.20",
"2.2.21",
"2.2.22",
"2.2.24",
"2.2.25",
"2.2.26",
"2.2.27",
"2.2.28",
"2.2.29",
"2.2.3",
"2.2.30",
"2.2.31",
"2.2.32",
"2.2.33",
"2.2.34",
"2.2.35",
"2.2.36",
"2.2.37",
"2.2.38",
"2.2.39",
"2.2.4",
"2.2.40",
"2.2.41",
"2.2.42",
"2.2.43",
"2.2.44",
"2.2.45",
"2.2.46",
"2.2.47",
"2.2.48",
"2.2.49",
"2.2.5",
"2.2.50",
"2.2.51",
"2.2.52",
"2.2.53",
"2.2.54",
"2.2.55",
"2.2.56",
"2.2.57",
"2.2.58",
"2.2.59",
"2.2.6",
"2.2.60",
"2.2.61",
"2.2.62",
"2.2.63",
"2.2.64",
"2.2.65",
"2.2.66",
"2.2.67",
"2.2.68",
"2.2.69",
"2.2.7",
"2.2.70",
"2.2.71",
"2.2.72",
"2.2.73",
"2.2.74",
"2.2.75",
"2.2.76",
"2.2.77",
"2.2.78",
"2.2.79",
"2.2.8",
"2.2.80",
"2.2.81",
"2.2.82",
"2.2.83",
"2.2.84",
"2.2.86",
"2.2.87",
"2.2.88",
"2.2.89",
"2.2.9",
"2.2.90",
"2.2.91",
"2.2.93",
"2.2.95",
"2.2.96",
"2.2.97",
"2.2.98",
"2.2.99",
"2.3.0",
"2.3.1",
"2.3.10",
"2.3.11",
"2.3.12",
"2.3.13",
"2.3.14",
"2.3.15",
"2.3.16",
"2.3.18",
"2.3.19",
"2.3.2",
"2.3.20",
"2.3.21",
"2.3.22",
"2.3.23",
"2.3.24",
"2.3.25",
"2.3.26",
"2.3.27",
"2.3.28",
"2.3.29",
"2.3.3",
"2.3.30",
"2.3.31",
"2.3.32",
"2.3.33",
"2.3.34",
"2.3.35",
"2.3.36",
"2.3.37",
"2.3.38",
"2.3.39",
"2.3.4",
"2.3.40",
"2.3.41",
"2.3.42",
"2.3.43",
"2.3.44",
"2.3.45",
"2.3.46",
"2.3.47",
"2.3.48",
"2.3.49",
"2.3.5",
"2.3.50",
"2.3.51",
"2.3.52",
"2.3.53",
"2.3.54",
"2.3.55",
"2.3.56",
"2.3.57",
"2.3.58",
"2.3.59",
"2.3.6",
"2.3.60",
"2.3.61",
"2.3.62",
"2.3.63",
"2.3.64",
"2.3.65",
"2.3.66",
"2.3.67",
"2.3.68",
"2.3.69",
"2.3.7",
"2.3.70",
"2.3.71",
"2.3.72",
"2.3.73",
"2.3.74",
"2.3.75",
"2.3.76",
"2.3.77",
"2.3.78",
"2.3.79",
"2.3.8",
"2.3.80",
"2.3.81",
"2.3.82",
"2.3.83",
"2.3.84",
"2.3.85",
"2.3.86",
"2.3.87",
"2.3.9",
"2.4.0",
"2.4.1",
"2.4.2",
"2.4.3",
"2.4.4",
"2.5.0",
"2.5.1",
"2.5.2",
"2.5.3",
"2.5.4",
"2.5.5",
"2.5.6",
"2.5.7",
"2.6.0",
"2.6.1",
"2.6.2",
"2.6.3",
"2.6.4",
"2.6.5",
"2.6.6",
"2.6.7",
"2.6.8",
"2.7.0",
"2.8.3",
"2.8.4",
"2.8.5",
"2.8.6",
"2.8.7",
"2.8.8",
"2.8.9",
"2.9.0",
"2.9.1",
"2.9.2",
"3.0.0",
"3.0.1",
"3.0.2",
"3.0.3",
"3.0.4",
"3.0.5",
"3.0.6",
"3.0.7",
"3.0.8",
"3.0.9",
"3.1.0",
"3.1.1",
"3.1.2",
"3.1.3",
"3.1.4",
"3.1.5",
"3.1.6",
"3.1.7",
"3.1.8",
"3.1.9",
"3.10.0",
"3.10.1",
"3.10.10",
"3.10.11",
"3.10.12",
"3.10.13",
"3.10.14",
"3.10.15",
"3.10.16",
"3.10.17",
"3.10.18",
"3.10.19",
"3.10.2",
"3.10.20",
"3.10.21",
"3.10.22",
"3.10.23",
"3.10.24",
"3.10.25",
"3.10.26",
"3.10.27",
"3.10.3",
"3.10.4",
"3.10.5",
"3.10.6",
"3.10.7",
"3.10.8",
"3.10.9",
"3.11.0",
"3.11.1",
"3.11.10",
"3.11.11",
"3.11.12",
"3.11.13",
"3.11.14",
"3.11.2",
"3.11.3",
"3.11.4",
"3.11.8",
"3.11.9",
"3.12.0",
"3.12.1",
"3.12.2",
"3.12.3",
"3.2.0",
"3.2.1",
"3.3.0",
"3.3.1",
"3.4.0",
"3.4.1",
"3.5.0",
"3.5.1",
"3.5.2",
"3.5.3",
"3.5.4",
"3.5.5",
"3.5.6",
"3.5.7",
"3.5.8",
"3.5.9",
"3.6.0",
"3.6.1",
"3.6.2",
"3.7.0",
"3.7.1",
"3.7.2",
"3.7.3",
"3.7.4",
"3.7.5",
"3.7.6",
"3.7.7",
"3.7.8",
"3.7.9",
"3.8.0",
"3.8.1",
"3.8.10",
"3.8.11",
"3.8.12",
"3.8.13",
"3.8.14",
"3.8.16",
"3.8.17",
"3.8.18",
"3.8.19",
"3.8.2",
"3.8.20",
"3.8.21",
"3.8.22",
"3.8.3",
"3.8.4",
"3.8.5",
"3.8.6",
"3.8.7",
"3.8.8",
"3.8.9",
"3.9.0",
"3.9.1",
"3.9.10",
"3.9.11",
"3.9.12",
"3.9.13",
"3.9.14",
"3.9.15",
"3.9.16",
"3.9.17",
"3.9.18",
"3.9.19",
"3.9.2",
"3.9.20",
"3.9.21",
"3.9.22",
"3.9.23",
"3.9.24",
"3.9.25",
"3.9.26",
"3.9.27",
"3.9.28",
"3.9.29",
"3.9.3",
"3.9.30",
"3.9.31",
"3.9.32",
"3.9.33",
"3.9.34",
"3.9.35",
"3.9.4",
"3.9.5",
"3.9.6",
"3.9.7",
"3.9.8",
"3.9.9",
"4.0.0",
"4.1.0",
"4.2.0",
"4.2.1",
"4.2.2",
"4.2.3",
"4.2.4",
"4.3.0",
"4.3.1",
"4.4.0",
"4.4.10",
"4.4.11",
"4.4.12",
"4.4.2",
"4.4.3",
"4.4.4",
"4.4.5",
"4.4.6",
"4.4.7",
"4.4.8",
"4.4.9",
"4.5.0",
"4.5.1",
"4.5.10",
"4.5.100",
"4.5.101",
"4.5.102",
"4.5.103",
"4.5.104",
"4.5.105",
"4.5.106",
"4.5.107",
"4.5.108",
"4.5.109",
"4.5.11",
"4.5.110",
"4.5.111",
"4.5.112",
"4.5.113",
"4.5.114",
"4.5.115",
"4.5.117",
"4.5.118",
"4.5.119",
"4.5.12",
"4.5.120",
"4.5.121",
"4.5.122",
"4.5.123",
"4.5.124",
"4.5.125",
"4.5.126",
"4.5.127",
"4.5.128",
"4.5.129",
"4.5.13",
"4.5.130",
"4.5.131",
"4.5.132",
"4.5.133",
"4.5.134",
"4.5.135",
"4.5.136",
"4.5.137",
"4.5.139",
"4.5.14",
"4.5.140",
"4.5.143",
"4.5.144",
"4.5.145",
"4.5.149",
"4.5.15",
"4.5.16",
"4.5.18",
"4.5.19",
"4.5.2",
"4.5.20",
"4.5.21",
"4.5.22",
"4.5.23",
"4.5.24",
"4.5.25",
"4.5.26",
"4.5.27",
"4.5.28",
"4.5.29",
"4.5.3",
"4.5.30",
"4.5.31",
"4.5.32",
"4.5.33",
"4.5.34",
"4.5.35",
"4.5.36",
"4.5.37",
"4.5.38",
"4.5.39",
"4.5.40",
"4.5.41",
"4.5.42",
"4.5.43",
"4.5.44",
"4.5.45",
"4.5.46",
"4.5.48",
"4.5.49",
"4.5.5",
"4.5.51",
"4.5.52",
"4.5.54",
"4.5.55",
"4.5.56",
"4.5.57",
"4.5.58",
"4.5.59",
"4.5.6",
"4.5.60",
"4.5.62",
"4.5.63",
"4.5.64",
"4.5.65",
"4.5.67",
"4.5.68",
"4.5.69",
"4.5.7",
"4.5.70",
"4.5.71",
"4.5.72",
"4.5.73",
"4.5.74",
"4.5.76",
"4.5.77",
"4.5.78",
"4.5.79",
"4.5.8",
"4.5.80",
"4.5.81",
"4.5.82",
"4.5.83",
"4.5.85",
"4.5.87",
"4.5.88",
"4.5.89",
"4.5.9",
"4.5.90",
"4.5.93",
"4.5.94",
"4.5.95",
"4.5.96",
"4.5.97",
"4.5.98",
"4.6.10",
"4.6.11",
"4.6.12",
"4.6.13",
"4.6.14",
"4.6.15",
"4.6.16",
"4.6.18",
"4.6.19",
"4.6.20",
"4.6.21",
"4.6.22",
"4.6.23",
"4.6.24",
"4.6.25",
"4.6.26",
"4.6.27",
"4.6.28",
"4.6.29",
"4.6.30",
"4.6.31",
"4.6.32",
"4.6.33",
"4.6.34",
"4.6.35",
"4.6.36",
"4.6.37",
"4.6.38",
"4.6.39",
"4.6.9"
]
}
],
"aliases": [
"CVE-2026-47393",
"GHSA-8444-4fhq-fxpq"
],
"details": "### Summary\n\nCVE-2026-44338 (GHSA-6rmh-7xcm-cpxj) documents that PraisonAI ships a code-generator (`praisonai.deploy.api.generate_api_server_code`) that emits a Flask API server with authentication disabled by default. Users who follow the documented quickstart (`praisonai deploy --type api`) get a server that:\n\n- binds to `0.0.0.0` per the recommended sample YAML\n- exposes `/chat` and `/agents` endpoints\n - runs `praisonai.run()` on user-supplied JSON input \u2014 LLM orchestration with the API key materials present in the process environment\n- does not require any authentication\n \nThe PyPI wheel `praisonai==4.6.33` (current `@latest`) still ships the generator with `auth_enabled` defaulting to `False`. The fix shape is opt-in via `APIConfig(auth_enabled=True, auth_token=...)`.\n\n### Details\n\n**Anchor (file:line:symbol)**\n\n- Vulnerable artifact: `praisonai==4.6.33` on PyPI.\n- Defaults: `praisonai/deploy/models.py:29` \u2014 `auth_enabled: bool = Field(default=False, ...)`; `praisonai/deploy/models.py:30` \u2014 `auth_token: Optional[str] = Field(default=None, ...)`.\n- Generator: `praisonai/deploy/api.py:40` \u2014 `AUTH_ENABLED = {config.auth_enabled}`; `api.py:41` \u2014 `AUTH_TOKEN = {repr(config.auth_token)}`; `api.py:43-49` \u2014 `def check_auth(): if not AUTH_ENABLED: return True`.\n- CLI entry: documented as `praisonai deploy --type api` (vendor README); produces the generator output above with no flag required to suppress the warning, because no warning is emitted.\n\n**Vulnerable code (verbatim from installed wheel)**\n\n```python\n# praisonai/deploy/models.py (praisonai==4.6.33)\nclass APIConfig(BaseModel):\n host: str = Field(default=\"127.0.0.1\", description=\"Server host\")\n port: int = Field(default=8005, description=\"Server port\")\n cors_enabled: bool = Field(default=True, description=\"Enable CORS\")\n auth_enabled: bool = Field(default=False, description=\"Enable authentication\") # line 29\n auth_token: Optional[str] = Field(default=None, description=\"Authentication token\") # line 30\n```\n\n```python\n # praisonai/deploy/api.py (praisonai==4.6.33)\ncode = f\\\u0027\\\u0027\\\u0027...\n# Authentication\n AUTH_ENABLED = {config.auth_enabled} # False by default\nAUTH_TOKEN = {repr(config.auth_token)} # None by default\n\ndef check_auth():\n if not AUTH_ENABLED:\n return True # short-circuit, accept all\n token = request.headers.get(\\\u0027Authorization\\\u0027, \\\u0027\\\u0027).replace(\\\u0027Bearer \\\u0027, \\\u0027\\\u0027)\n return token == AUTH_TOKEN\n...\n\\\u0027\\\u0027\\\u0027\n```\n\nA default invocation of the deploy command emits a server whose `check_auth()` short-circuits to `True` and accepts unauthenticated `/chat`, `/agents` POSTs.\n\n### PoC\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nlegend-c420 PoC - PraisonAI 4.6.33 generates Flask API server with auth\ndisabled by default. Class H sibling of CVE-2026-44338.\n\nPhase 1: reflect on praisonai.deploy.models.APIConfig defaults.\nPhase 2: call generate_api_server_code(default config) and assert the\n emitted source contains AUTH_ENABLED = False and the\n short-circuit return.\nPhase 3: re-run with auth_enabled=True, auth_token=\u0027s3cret-bearer-value\u0027\n and confirm the emitted source flips to the secure shape.\n\nExit code 0 = PASS = vulnerable defaults confirmed.\n\"\"\"\nimport sys, traceback\n\ndef phase1_dataclass_defaults():\n print(\"PHASE 1 - praisonai.deploy.models.APIConfig default values\")\n from praisonai.deploy.models import APIConfig\n cfg = APIConfig()\n checks = [\n (\"auth_enabled\", cfg.auth_enabled, False),\n (\"auth_token\", cfg.auth_token, None),\n ]\n for name, observed, expected in checks:\n ok = observed == expected\n mark = \"VULNERABLE\" if name in (\"auth_enabled\",\"auth_token\") and ok else \"ok\"\n print(f\" {name:14s} = {observed!r:18s} (expected {expected!r}) [{mark}]\")\n assert ok\n print(\" \u003e\u003e APIConfig defaults reproduce the CVE-2026-44338 shape.\")\n\ndef phase2_default_generator_emits_unauth():\n print(\"PHASE 2 - generate_api_server_code(default config) emits unauth server\")\n from praisonai.deploy.models import APIConfig\n from praisonai.deploy.api import generate_api_server_code\n src = generate_api_server_code(\"agents.yaml\", config=APIConfig())\n for needle in [\"AUTH_ENABLED = False\",\"AUTH_TOKEN = None\",\"if not AUTH_ENABLED:\",\"return True\"]:\n assert needle in src, f\"missing: {needle!r}\"\n print(f\" [FOUND] {needle!r}\")\n print(\" \u003e\u003e Default-config generator emits Flask server with check_auth() short-circuit.\")\n\ndef phase3_fix_shape_available():\n print(\"PHASE 3 - auth_enabled=True flips to secure shape\")\n from praisonai.deploy.models import APIConfig\n from praisonai.deploy.api import generate_api_server_code\n cfg = APIConfig(auth_enabled=True, auth_token=\"s3cret-bearer-value\")\n src = generate_api_server_code(\"agents.yaml\", config=cfg)\n assert \"AUTH_ENABLED = True\" in src\n assert \"AUTH_ENABLED = False\" not in src\n print(\" \u003e\u003e Fix shape works when toggled. Class H confirmed: default is insecure.\")\n\ndef main():\n print(\"=\" * 64)\n print(\"legend-c420 PoC - PraisonAI default-config AUTH_ENABLED=False\")\n print(\"=\" * 64)\n try:\n phase1_dataclass_defaults()\n phase2_default_generator_emits_unauth()\n phase3_fix_shape_available()\n except Exception:\n traceback.print_exc()\n print(\"FAIL\"); sys.exit(2)\n print(\"PASS 3/3 phases. EXIT 0.\")\n sys.exit(0)\n\nif __name__ == \"__main__\":\n main()\n```\n\n**PoC dependencies:** `praisonai==4.6.33` from PyPI. Tested on Python 3.11.\n\n**Run log verdict:** `PASS 3/3 phases. EXIT 0.` \u2014 vulnerable-default shape confirmed. `auth_enabled=False` by default, `check_auth()` short-circuits to `True`, fix toggle exists but is opt-in.\n \n### Impact\n\nAn operator who runs the vendor-documented quickstart (`pip install praisonai \u0026\u0026 praisonai deploy --type api`) gets a network-reachable Flask server that invokes `praisonai.run()` on attacker-supplied JSON with the user\u0027s LLM API keys in the process environment. The attacker reaches arbitrary LLM-orchestration (including any tool-use the agents define, which in PraisonAI commonly includes `python_repl`, `bash`, file I/O, and HTTP calls), with the host\u0027s API-key credit billed to the operator.\n\n- **Belief:** CVE-2026-44338 was filed and triaged.\n - **Reality:** `praisonai==4.6.33` is current `@latest` on PyPI (2026-05-16). The generator still defaults to `auth_enabled=False`.\n- **Gap:** The CVE acknowledges the fix shape exists. The fix is opt-in. The default-config consumer remains vulnerable.\n \n**Parent CVE:** CVE-2026-44338 / GHSA-6rmh-7xcm-cpxj",
"id": "PYSEC-2026-465",
"modified": "2026-07-01T20:23:01.228154Z",
"published": "2026-06-29T11:50:50.186039Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-8444-4fhq-fxpq"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-6rmh-7xcm-cpxj"
},
{
"type": "PACKAGE",
"url": "https://pypi.org/project/praisonai"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-8444-4fhq-fxpq"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-47393"
}
],
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "PraisonAI `deploy --type api` emits a Flask server with authentication disabled by default"
}
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.