Skip to content

Warn when CodeExecutor runs LLM-generated code without a Sandbox#1890

Open
TurtlesMaster1 wants to merge 1 commit into
sinaptik-ai:mainfrom
TurtlesMaster1:warn/unsandboxed-code-execution
Open

Warn when CodeExecutor runs LLM-generated code without a Sandbox#1890
TurtlesMaster1 wants to merge 1 commit into
sinaptik-ai:mainfrom
TurtlesMaster1:warn/unsandboxed-code-execution

Conversation

@TurtlesMaster1
Copy link
Copy Markdown

Summary

CodeExecutor.execute runs LLM-generated Python via exec(code, environment) where environment is the dict from get_environment(){"pd": pandas, "plt": matplotlib.pyplot, "np": numpy}. Because the dict has no __builtins__ key, CPython silently inserts the full builtins module, so the generated code can call __import__("os").system(...), open(...), subprocess.run(...), and read environment variables.

pandasai/sandbox/ and the pandasai-docker extension exist for the secure case, but sandbox is an optional parameter on Agent (sandbox: Sandbox = None) and the unsandboxed default is silent.

A user who follows the quickstart and does not pass sandbox= to Agent is exec'ing untrusted-LLM-generated code with full host privileges. This includes the indirect prompt-injection vector — a value inside a CSV column or a tool response can steer the LLM into emitting code that runs on the host.

What this PR changes

Minimal, fully backward compatible:

  1. CodeExecutor keeps a reference to the Config it was constructed with.
  2. The first time CodeExecutor.execute runs in a process without a sandbox path, emit a RuntimeWarning (and a logger.warning) explaining the risk and pointing to the DockerSandbox extension.
  3. The warning fires at most once per process. It can be silenced with Config(suppress_unsandboxed_warning=True) for users who have considered the trade-off.

Behavior of existing code paths is unchanged — the warning is purely informational. exec still runs with the same environment.

Why a warning rather than a default-deny

A default-deny (e.g. raising in execute unless a sandbox is configured) would be safer but would break every existing quickstart-style integration. That's a maintainer call. This PR is the cheapest safety upgrade — strictly additive, no behavior change beyond a one-shot stderr warning — and leaves the door open for a future PR that defaults to a restricted-builtins environment when no sandbox is supplied.

Severity

The unsandboxed default is, in practice, RCE-via-prompt-injection: any user input the LLM consumes (a prompt, a CSV cell, an API tool response) becomes a code-execution vector on the host. Config(suppress_unsandboxed_warning=True) documents the opt-in nature of the risk; Agent(..., sandbox=DockerSandbox()) is the documented secure path.

Related disclosures

Cross-project pattern audit of LLM-code-execution sandboxes in AI agent frameworks. Companion PRs for related (but distinct) sandbox bugs in two other frameworks: huggingface/smolagents#2271, run-llama/llama_index#21633.

Happy to follow up with a stricter default in a separate PR if maintainers want to discuss the breaking-change trade-off.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant