PT-2026-33895 · Pypi · Praisonaiagents
Published
2026-04-10
·
Updated
2026-04-10
CVSS v3.1
5.5
Medium
| Vector | AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N |
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 approvalThe
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-178Impact
- 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
ContextVarthat 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
- Per-invocation approval for critical tools — store a hash of
(tool name, arguments)instead of justtool 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- 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)Fix
Incorrect Authorization
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Praisonaiagents