PYSEC-2026-2946
PraisonAI: Coarse-Grained Tool Approval Cache Bypasses Per-Invocation Consent for Shell Commands
Опубликована: 13.07.2026, 17:36 · Изменена: 03.11.2026, 11:15
## Summary The approval system in PraisonAI Agents caches tool approval decisions by tool name only, not by invocation arguments. Once a user approves `execute_command` for any command (e.g., `ls -la`), all subsequent `execute_command` calls in that execution context bypass the approval prompt entirely. Combined with `os.environ.copy()` passing all process environment variables to subprocesses, this allows an LLM agent (potentially via prompt injection) to silently exfiltrate API keys and credentials without further user consent. ## Details The `require_approval` decorator in `src/praisonai-agents/praisonaiagents/approval/__init__.py:176-178` checks approval status by tool name only: ```python @wraps(func) def wrapper(*args, **kwargs): if is_already_approved(tool_name): # line 177 — checks only tool_name return func(*args, **kwargs) # line 178 — bypasses ALL approval ``` The `mark_approved` function in `registry.py:144-147` stores only the tool name string: ```python def mark_approved(self, tool_name: str) -> None: approved = self._approved_context.get(set()) approved.add(tool_name) # stores "execute_command", not args self._approved_context.set(approved) ``` The approval context is never cleared during agent execution — `clear_approved()` exists (`registry.py:152`) but is never called in the agent's tool execution path (`agent/tool_execution.py`). Meanwhile, the `ConsoleBackend` UI at `backends.py:95-96` misleads the user: ```python return Confirm.ask( f"Do you want to execute this {request.risk_level} risk tool?", # "this" implies per-invocation approval ) ``` The UI displays the specific command arguments (lines 81-85), creating a reasonable expectation that the user is approving only that specific invocation. Additionally, `shell_tools.py:77` passes the full process environment to every subprocess: ```python process_env = os.environ.copy() # includes OPENAI_API_KEY, etc. ``` There is no command filtering, blocklist, or environment variable sanitization in the shell tools module. ## PoC ```python from praisonaiagents import Agent from praisonaiagents.tools.shell_tools import execute_command # Step 1: Create agent with shell tool agent = Agent( name="worker", instructions="You are a helpful assistant.", tools=[execute_command] ) # Step 2: Agent requests benign command — user sees Rich panel: # Function: execute_command # Risk Level: CRITICAL # Arguments: # command: ls -la # "Do you want to execute this critical risk tool?" [y/N] # User approves → mark_approved("execute_command") is called # Step 3: All subsequent execute_command calls bypass approval silently: # execute_command(command="env") # → returns ALL environment variables (OPENAI_API_KEY, AWS_SECRET_ACCESS_KEY, etc.) # → NO approval prompt shown # Step 4: Targeted extraction also bypasses approval: # execute_command(command="printenv OPENAI_API_KEY") # → returns the specific API key # → NO approval prompt shown # Verification: check the approval cache from praisonaiagents.approval import is_already_approved # After approving "ls -la": # is_already_approved("execute_command") → True # Any execute_command call now returns immediately at __init__.py:177-178 ``` ## Impact - **Secret exfiltration**: An LLM agent (or one subjected to prompt injection) can dump all process environment variables after a single benign command approval. Common secrets include `OPENAI_API_KEY`, `AWS_SECRET_ACCESS_KEY`, `DATABASE_URL`, and any other credentials passed via environment. - **Misleading consent UI**: The console prompt displays specific arguments and uses language ("this tool") that implies per-invocation consent, but the system grants session-wide blanket approval. - **No expiration or scope**: The approval cache uses a `ContextVar` that persists for the entire agent execution context with no timeout, no command-count limit, and no clearing between tool calls. - **No environment filtering**: `os.environ.copy()` passes every environment variable to subprocesses without filtering sensitive patterns. ## Recommended Fix 1. **Per-invocation approval for critical tools** — store a hash of `(tool_name, arguments)` instead of just `tool_name`, or require re-approval for each invocation of critical-risk tools: ```python # In registry.py — change mark_approved/is_already_approved: import hashlib, json def mark_approved(self, tool_name: str, arguments: dict = None) -> None: approved = self._approved_context.get(set()) risk = self._risk_levels.get(tool_name) if risk == "critical" and arguments: key = f"{tool_name}:{hashlib.sha256(json.dumps(arguments, sort_keys=True).encode()).hexdigest()}" else: key = tool_name approved.add(key) self._approved_context.set(approved) def is_already_approved(self, tool_name: str, arguments: dict = None) -> bool: approved = self._approved_context.get(set()) risk = self._risk_levels.get(tool_name) if risk == "critical" and arguments: key = f"{tool_name}:{hashlib.sha256(json.dumps(arguments, sort_keys=True).encode()).hexdigest()}" return key in approved return tool_name in approved ``` 2. **Filter environment variables** in `shell_tools.py`: ```python SENSITIVE_PATTERNS = ('_KEY', '_SECRET', '_TOKEN', '_PASSWORD', '_CREDENTIAL') process_env = { k: v for k, v in os.environ.items() if not any(p in k.upper() for p in SENSITIVE_PATTERNS) } if env: process_env.update(env) ```
Связанные идентификаторы
Альтернативные ID: CVE-2026-56074, GHSA-ffp3-3562-8cv3
Связанные записи: —
Исходные уязвимости: —
Оценки источника
CVSS_V3: CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
Связь с записью и оценка источника сами по себе не подтверждают, что уязвимость затрагивает установленный компонент. Результаты проверки вашего пакета доступны в «Моём стеке».
praisonaiagents
PyPI
pkg:pypi/praisonaiagents
Диапазоны и версии из источника
{
"ranges": [
{
"type": "ECOSYSTEM",
"events": [
{
"introduced": "0"
},
{
"fixed": "4.5.128"
}
]
}
],
"versions": [
"0.0.1",
"0.0.10",
"0.0.100",
"0.0.101",
"0.0.102",
"0.0.103",
"0.0.104",
"0.0.105",
"0.0.106",
"0.0.107",
"0.0.108",
"0.0.109",
"0.0.11",
"0.0.110",
"0.0.111",
"0.0.112",
"0.0.113",
"0.0.114",
"0.0.115",
"0.0.116",
"0.0.117",
"0.0.118",
"0.0.119",
"0.0.12",
"0.0.120",
"0.0.121",
"0.0.122",
"0.0.123",
"0.0.124",
"0.0.125",
"0.0.126",
"0.0.127",
"0.0.128",
"0.0.129",
"0.0.13",
"0.0.130",
"0.0.131",
"0.0.132",
"0.0.133",
"0.0.134",
"0.0.135",
"0.0.136",
"0.0.137",
"0.0.138",
"0.0.139",
"0.0.14",
"0.0.140",
"0.0.141",
"0.0.142",
"0.0.143",
"0.0.144",
"0.0.145",
"0.0.146",
"0.0.147",
"0.0.148",
"0.0.149",
"0.0.15",
"0.0.150",
"0.0.151",
"0.0.152",
"0.0.153",
"0.0.154",
"0.0.155",
"0.0.156",
"0.0.157",
"0.0.158",
"0.0.159",
"0.0.16",
"0.0.160",
"0.0.161",
"0.0.162",
"0.0.163",
"0.0.164",
"0.0.165",
"0.0.166",
"0.0.167",
"0.0.168",
"0.0.169",
"0.0.17",
"0.0.170",
"0.0.171",
"0.0.172",
"0.0.173",
"0.0.174",
"0.0.175",
"0.0.176",
"0.0.177",
"0.0.178",
"0.0.179",
"0.0.18",
"0.0.180",
"0.0.181",
"0.0.182",
"0.0.183",
"0.0.184",
"0.0.185",
"0.0.187",
"0.0.188",
"0.0.189",
"0.0.19",
"0.0.190",
"0.0.191",
"0.0.192",
"0.0.193",
"0.0.194",
"0.0.195",
"0.0.196",
"0.0.197",
"0.0.198",
"0.0.199",
"0.0.2",
"0.0.20",
"0.0.21",
"0.0.22",
"0.0.23",
"0.0.24",
"0.0.25",
"0.0.26",
"0.0.27",
"0.0.28",
"0.0.29",
"0.0.3",
"0.0.30",
"0.0.31",
"0.0.32",
"0.0.33",
"0.0.34",
"0.0.35",
"0.0.36",
"0.0.37",
"0.0.38",
"0.0.39",
"0.0.4",
"0.0.40",
"0.0.41",
"0.0.42",
"0.0.43",
"0.0.44",
"0.0.45",
"0.0.46",
"0.0.47",
"0.0.48",
"0.0.49",
"0.0.5",
"0.0.50",
"0.0.51",
"0.0.52",
"0.0.53",
"0.0.54",
"0.0.56",
"0.0.57",
"0.0.58",
"0.0.59",
"0.0.6",
"0.0.60",
"0.0.61",
"0.0.62",
"0.0.63",
"0.0.64",
"0.0.65",
"0.0.66",
"0.0.67",
"0.0.68",
"0.0.69",
"0.0.7",
"0.0.70",
"0.0.71",
"0.0.72",
"0.0.73",
"0.0.74",
"0.0.75",
"0.0.76",
"0.0.77",
"0.0.78",
"0.0.79",
"0.0.8",
"0.0.80",
"0.0.81",
"0.0.82",
"0.0.83",
"0.0.84",
"0.0.85",
"0.0.86",
"0.0.87",
"0.0.88",
"0.0.89",
"0.0.9",
"0.0.90",
"0.0.91",
"0.0.92",
"0.0.93",
"0.0.94",
"0.0.95",
"0.0.96",
"0.0.97",
"0.0.98",
"0.0.99",
"0.1.0",
"0.1.1",
"0.1.10",
"0.1.11",
"0.1.12",
"0.1.13",
"0.1.14",
"0.1.15",
"0.1.16",
"0.1.17",
"0.1.18",
"0.1.19",
"0.1.2",
"0.1.20",
"0.1.21",
"0.1.22",
"0.1.23",
"0.1.24",
"0.1.25",
"0.1.26",
"0.1.27",
"0.1.3",
"0.1.4",
"0.1.5",
"0.1.6",
"0.1.7",
"0.1.8",
"0.1.9",
"0.10.0",
"0.10.1",
"0.10.10",
"0.10.2",
"0.10.3",
"0.10.4",
"0.10.5",
"0.10.6",
"0.10.7",
"0.10.8",
"0.10.9",
"0.11.0",
"0.11.1",
"0.11.10",
"0.11.11",
"0.11.12",
"0.11.13",
"0.11.14",
"0.11.15",
"0.11.16",
"0.11.17",
"0.11.18",
"0.11.19",
"0.11.2",
"0.11.20",
"0.11.21",
"0.11.22",
"0.11.23",
"0.11.24",
"0.11.25",
"0.11.27",
"0.11.28",
"0.11.29",
"0.11.3",
"0.11.30",
"0.11.31",
"0.11.4",
"0.11.5",
"0.11.6",
"0.11.7",
"0.11.8",
"0.11.9",
"0.12.0",
"0.12.1",
"0.12.10",
"0.12.11",
"0.12.12",
"0.12.13",
"0.12.14",
"0.12.15",
"0.12.16",
"0.12.17",
"0.12.18",
"0.12.19",
"0.12.2",
"0.12.20",
"0.12.21",
"0.12.3",
"0.12.4",
"0.12.5",
"0.12.6",
"0.12.7",
"0.12.8",
"0.12.9",
"0.13.0",
"0.13.1",
"0.13.10",
"0.13.11",
"0.13.12",
"0.13.13",
"0.13.14",
"0.13.15",
"0.13.16",
"0.13.17",
"0.13.18",
"0.13.19",
"0.13.2",
"0.13.20",
"0.13.21",
"0.13.22",
"0.13.23",
"0.13.3",
"0.13.4",
"0.13.5",
"0.13.6",
"0.13.7",
"0.13.8",
"0.13.9",
"0.14.0",
"0.14.1",
"0.14.10",
"0.14.11",
"0.14.12",
"0.14.14",
"0.14.15",
"0.14.16",
"0.14.2",
"0.14.3",
"0.14.4",
"0.14.5",
"0.14.6",
"0.14.7",
"0.14.8",
"0.14.9",
"0.15.0",
"0.15.1",
"0.15.2",
"0.15.3",
"0.2.0",
"0.2.1",
"0.2.2",
"0.3.0",
"0.3.1",
"0.3.2",
"0.3.3",
"0.3.4",
"0.4.0",
"0.4.1",
"0.5.0",
"0.5.1",
"0.5.2",
"0.5.3",
"0.6.0",
"0.6.1",
"0.6.2",
"0.6.3",
"0.6.4",
"0.6.5",
"0.6.6",
"0.6.7",
"0.6.8",
"0.7.0",
"0.7.1",
"0.8.0",
"0.8.1",
"0.9.0",
"0.9.1",
"1.0.0",
"1.1.0",
"1.2.0",
"1.2.1",
"1.2.2",
"1.2.3",
"1.2.4",
"1.3.0",
"1.3.1",
"1.4.0",
"1.4.1",
"1.4.2",
"1.4.3",
"1.4.4",
"1.4.5",
"1.4.6",
"1.4.7",
"1.4.8",
"1.5.0",
"1.5.1",
"1.5.10",
"1.5.100",
"1.5.101",
"1.5.102",
"1.5.103",
"1.5.104",
"1.5.105",
"1.5.106",
"1.5.107",
"1.5.108",
"1.5.109",
"1.5.11",
"1.5.110",
"1.5.111",
"1.5.112",
"1.5.113",
"1.5.114",
"1.5.115",
"1.5.116",
"1.5.117",
"1.5.118",
"1.5.119",
"1.5.12",
"1.5.120",
"1.5.121",
"1.5.122",
"1.5.123",
"1.5.124",
"1.5.125",
"1.5.126",
"1.5.127",
"1.5.128",
"1.5.129",
"1.5.13",
"1.5.130",
"1.5.131",
"1.5.132",
"1.5.133",
"1.5.134",
"1.5.135",
"1.5.136",
"1.5.137",
"1.5.138",
"1.5.139",
"1.5.14",
"1.5.140",
"1.5.141",
"1.5.142",
"1.5.143",
"1.5.144",
"1.5.145",
"1.5.146",
"1.5.147",
"1.5.148",
"1.5.149",
"1.5.15",
"1.5.16",
"1.5.17",
"1.5.18",
"1.5.19",
"1.5.2",
"1.5.20",
"1.5.21",
"1.5.22",
"1.5.23",
"1.5.24",
"1.5.25",
"1.5.26",
"1.5.27",
"1.5.28",
"1.5.29",
"1.5.3",
"1.5.30",
"1.5.31",
"1.5.32",
"1.5.33",
"1.5.34",
"1.5.35",
"1.5.36",
"1.5.37",
"1.5.38",
"1.5.39",
"1.5.40",
"1.5.41",
"1.5.42",
"1.5.43",
"1.5.44",
"1.5.45",
"1.5.46",
"1.5.47",
"1.5.48",
"1.5.49",
"1.5.5",
"1.5.50",
"1.5.51",
"1.5.52",
"1.5.53",
"1.5.54",
"1.5.55",
"1.5.56",
"1.5.57",
"1.5.58",
"1.5.59",
"1.5.6",
"1.5.60",
"1.5.61",
"1.5.62",
"1.5.63",
"1.5.64",
"1.5.65",
"1.5.66",
"1.5.67",
"1.5.68",
"1.5.69",
"1.5.7",
"1.5.70",
"1.5.71",
"1.5.72",
"1.5.73",
"1.5.74",
"1.5.75",
"1.5.76",
"1.5.77",
"1.5.78",
"1.5.79",
"1.5.8",
"1.5.80",
"1.5.81",
"1.5.82",
"1.5.83",
"1.5.84",
"1.5.85",
"1.5.86",
"1.5.87",
"1.5.88",
"1.5.89",
"1.5.9",
"1.5.90",
"1.5.91",
"1.5.92",
"1.5.93",
"1.5.94",
"1.5.95",
"1.5.96",
"1.5.97",
"1.5.98",
"1.5.99",
"1.6.1",
"1.6.10",
"1.6.100",
"1.6.101",
"1.6.102",
"1.6.103",
"1.6.104",
"1.6.105",
"1.6.106",
"1.6.107",
"1.6.108",
"1.6.109",
"1.6.11",
"1.6.110",
"1.6.111",
"1.6.112",
"1.6.113",
"1.6.114",
"1.6.115",
"1.6.116",
"1.6.117",
"1.6.118",
"1.6.119",
"1.6.12",
"1.6.120",
"1.6.123",
"1.6.124",
"1.6.125",
"1.6.126",
"1.6.127",
"1.6.128",
"1.6.129",
"1.6.13",
"1.6.130",
"1.6.131",
"1.6.132",
"1.6.133",
"1.6.134",
"1.6.135",
"1.6.136",
"1.6.137",
"1.6.138",
"1.6.139",
"1.6.14",
"1.6.140",
"1.6.141",
"1.6.142",
"1.6.143",
"1.6.144",
"1.6.145",
"1.6.146",
"1.6.147",
"1.6.148",
"1.6.149",
"1.6.15",
"1.6.150",
"1.6.151",
"1.6.152",
"1.6.153",
"1.6.154",
"1.6.155",
"1.6.156",
"1.6.157",
"1.6.158",
"1.6.159",
"1.6.16",
"1.6.160",
"1.6.161",
"1.6.162",
"1.6.163",
"1.6.164",
"1.6.165",
"1.6.166",
"1.6.167",
"1.6.168",
"1.6.17",
"1.6.18",
"1.6.19",
"1.6.2",
"1.6.20",
"1.6.21",
"1.6.22",
"1.6.23",
"1.6.24",
"1.6.25",
"1.6.26",
"1.6.27",
"1.6.28",
"1.6.29",
"1.6.3",
"1.6.30",
"1.6.31",
"1.6.32",
"1.6.33",
"1.6.34",
"1.6.35",
"1.6.36",
"1.6.37",
"1.6.38",
"1.6.39",
"1.6.4",
"1.6.40",
"1.6.41",
"1.6.42",
"1.6.43",
"1.6.44",
"1.6.45",
"1.6.46",
"1.6.47",
"1.6.48",
"1.6.5",
"1.6.50",
"1.6.51",
"1.6.52",
"1.6.53",
"1.6.54",
"1.6.55",
"1.6.56",
"1.6.57",
"1.6.58",
"1.6.59",
"1.6.6",
"1.6.60",
"1.6.62",
"1.6.63",
"1.6.64",
"1.6.65",
"1.6.66",
"1.6.67",
"1.6.68",
"1.6.69",
"1.6.7",
"1.6.70",
"1.6.71",
"1.6.72",
"1.6.73",
"1.6.74",
"1.6.75",
"1.6.76",
"1.6.77",
"1.6.78",
"1.6.79",
"1.6.8",
"1.6.80",
"1.6.81",
"1.6.82",
"1.6.83",
"1.6.84",
"1.6.85",
"1.6.86",
"1.6.87",
"1.6.88",
"1.6.89",
"1.6.9",
"1.6.90",
"1.6.91",
"1.6.92",
"1.6.93",
"1.6.94",
"1.6.95",
"1.6.96",
"1.6.97",
"1.6.99",
"1.7.0",
"1.7.1",
"1.7.10",
"1.7.11",
"1.7.12",
"1.7.13",
"1.7.14",
"1.7.15",
"1.7.16",
"1.7.17",
"1.7.18",
"1.7.19",
"1.7.2",
"1.7.20",
"1.7.21",
"1.7.3",
"1.7.4",
"1.7.5",
"1.7.6",
"1.7.7",
"1.7.8",
"1.7.9"
]
}Ссылки источника
- WEB: https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-ffp3-3562-8cv3
- ADVISORY: https://nvd.nist.gov/vuln/detail/CVE-2026-56074
- PACKAGE: https://github.com/MervinPraison/PraisonAI
- WEB: https://github.com/MervinPraison/PraisonAI/releases/tag/v4.5.128
- WEB: https://www.vulncheck.com/advisories/praisonai-tool-approval-cache-bypass-via-coarse-grained-caching
- PACKAGE: https://pypi.org/project/praisonaiagents
- ADVISORY: https://github.com/advisories/GHSA-ffp3-3562-8cv3