GitHub Security Advisory: GHSA-v8g7-9q6v-p3x8
Package: praisonaiagents (PIP)
Severity: HIGH (CVSS 7.4)
CWE: CWE-526: Cleartext Storage of Sensitive Information in an Environment Variable
Vulnerable versions: < 1.5.128
Patched in: 1.5.128

Summary: PraisonAIAgents: Environment Variable Secret Exfiltration via os.path.expandvars() Bypassing shell=False in Shell Tool

Details:
## Summary

The `execute_command` function in `shell_tools.py` calls `os.path.expandvars()` on every command argument at line 64, manually re-implementing shell-level environment variable expansion despite using `shell=False` (line 88) for security. This allows exfiltration of secrets stored in environment variables (database credentials, API keys, cloud access keys). The approval system displays the **unexpanded** `$VAR` references to human reviewers, creating a deceptive approval where the displayed command differs from what actually executes.

## Details

The vulnerable code is in `src/praisonai-agents/praisonaiagents/tools/shell_tools.py`:

```python
# Line 60: command is split
command = shlex.split(command)

# Lines 62-64: VULNERABLE — expands ALL env vars in every argument
# Expand tilde and environment variables in command arguments
# (shell=False means the shell won't do this for us)
command = [os.path.expanduser(os.path.expandvars(arg)) for arg in command]

# Line 88: shell=False is supposed to prevent shell feature access
process = subprocess.Popen(
    command,
    ...
    shell=False,  # Always use shell=False for security
)
```

The security problem is a disconnect between the approval display and actual execution:

1. The LLM generates a tool call: `execute_command(command="cat $DATABASE_URL")`
2. `_check_tool_approval_sync` in `tool_execution.py:558` passes `{"command": "cat $DATABASE_URL"}` to the approval backend
3. `ConsoleBackend` (backends.py:81-85) displays `command: cat $DATABASE_URL` — the literal dollar-sign form
4. The user approves, reasoning that `shell=False` prevents variable expansion
5. Inside `execute_command`, `os.path.expandvars("$DATABASE_URL")` → `postgres://user:secretpass@prod-host:5432/mydb`
6. The expanded secret appears in stdout, returned to the LLM

Line 69 has the same issue for the `cwd` parameter:
```python
cwd = os.path.expandvars(cwd)  # Also expand $HOME, $USER, etc.
```

With `PRAISONAI_AUTO_APPROVE=true` (registry.py:170-171), `AutoApproveBackend`, YAML-approved tools, or `AgentApproval`, no human reviews the command at all. The env var auto-approve check is:

```python
# registry.py:170-171
@staticmethod
def is_env_auto_approve() -> bool:
    return os.environ.get("PRAISONAI_AUTO_APPROVE", "").lower() in ("true", "1", "yes")
```

## PoC

```python
import os

# Simulate secrets in environment (common in production/CI)
os.environ['DATABASE_URL'] = 'postgres://admin:s3cretP@ss@prod-db.internal:5432/app'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'

# Enable auto-approve (as used in CI/automated deployments)
os.environ['PRAISONAI_AUTO_APPROVE'] = 'true'

from praisonaiagents.tools.shell_tools import ShellTools
st = ShellTools()

# The approval system (if it were manual) would show: echo $DATABASE_URL
# But expandvars resolves it before execution
result = st.execute_command(command='echo $DATABASE_URL $AWS_SECRET_ACCESS_KEY')

print("stdout:", result['stdout'])
# s