Skip to content

Commit 672572f

Browse files
Warn loudly when CodeExecutor runs LLM code without a Sandbox
When an Agent is constructed without an explicit `sandbox=` parameter, `Agent.execute_code` falls through to `CodeExecutor.execute_and_return_result`, which calls `exec(code, environment)` where `environment` is `{'pd': pandas, 'plt': pyplot, 'np': numpy}`. Because the environment has no `__builtins__` key, CPython silently inserts the full builtins module, so LLM-generated code can call `__import__('os').system(...)`, `open(...)`, `subprocess.run(...)`, and read environment variables. The Docker sandbox extension is the supported mitigation but is opt-in and not loud about the unsandboxed default. Users who follow the quickstart and don't pass `sandbox=` to `Agent` are silently exposing their host to anyone who can influence the LLM's input (a malicious user prompt, a poisoned CSV column, a tool response). This commit adds a one-shot `RuntimeWarning` plus a `logger.warning` the first time `CodeExecutor.execute` runs without a sandbox in a given process. The warning explains the risk, points to the DockerSandbox mitigation, and offers an opt-out (`Config(suppress_unsandboxed_warning=True)`) for users who have considered the trade-off and want to silence the message. No behavior change beyond the warning — exec() still runs as before so this PR is backward compatible. A follow-up PR can propose a restricted-builtins default for users who don't pass a sandbox.
1 parent bbbb771 commit 672572f

1 file changed

Lines changed: 39 additions & 0 deletions

File tree

pandasai/core/code_execution/code_executor.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
1+
import logging
2+
import warnings
13
from typing import Any
24

35
from pandasai.config import Config
46
from pandasai.core.code_execution.environment import get_environment
57
from pandasai.exceptions import CodeExecutionError, NoResultFoundError
68

79

10+
logger = logging.getLogger(__name__)
11+
12+
13+
_SANDBOX_WARNING = (
14+
"pandas-ai is about to execute LLM-generated Python code on this host "
15+
"without a sandbox. This is the historical default but it is unsafe: "
16+
"if the LLM is influenced by untrusted input (a malicious prompt, a "
17+
"crafted CSV column, a poisoned tool response) the generated code can "
18+
"read files, exfiltrate environment variables, and run arbitrary "
19+
"subprocesses. Configure a Sandbox (e.g. pandasai_docker.DockerSandbox) "
20+
"via Agent(..., sandbox=...) to mitigate this. Pass "
21+
"Config(suppress_unsandboxed_warning=True) to silence this warning."
22+
)
23+
24+
825
class CodeExecutor:
926
"""
1027
Handle the logic on how to handle different lines of code
@@ -14,6 +31,7 @@ class CodeExecutor:
1431

1532
def __init__(self, config: Config) -> None:
1633
self._environment = get_environment()
34+
self._config = config
1735

1836
def add_to_env(self, key: str, value: Any) -> None:
1937
"""
@@ -24,7 +42,28 @@ def add_to_env(self, key: str, value: Any) -> None:
2442
"""
2543
self._environment[key] = value
2644

45+
def _emit_sandbox_warning_once(self) -> None:
46+
"""Warn (once per process) that code is about to run without a sandbox.
47+
48+
``exec(code, env)`` where ``env`` has no ``__builtins__`` key causes
49+
Python to silently insert the full ``builtins`` module, including
50+
``__import__``, ``open``, ``eval``, ``exec``. LLM-generated code
51+
therefore has unrestricted access to the host. The DockerSandbox
52+
extension (or any subclass of :class:`pandasai.sandbox.Sandbox`)
53+
is the supported mitigation; this warning makes the unsandboxed
54+
default explicit so that users who haven't configured a sandbox
55+
notice and choose intentionally.
56+
"""
57+
if getattr(self._config, "suppress_unsandboxed_warning", False):
58+
return
59+
if getattr(CodeExecutor, "_warned_unsandboxed", False):
60+
return
61+
CodeExecutor._warned_unsandboxed = True
62+
warnings.warn(_SANDBOX_WARNING, RuntimeWarning, stacklevel=3)
63+
logger.warning(_SANDBOX_WARNING)
64+
2765
def execute(self, code: str) -> dict:
66+
self._emit_sandbox_warning_once()
2867
try:
2968
exec(code, self._environment)
3069
except Exception as e:

0 commit comments

Comments
 (0)