#8239·autogen

CodeExecutorAgent defaults execute any participant's code blocks on the host without approval; documented dangerous-command sanitizer does not exist

Author: AUTHENSORCreated Sep 15, 2026Updated Sep 15, 2026

CodeExecutorAgent defaults execute any participant's code blocks on the host with automatic approval, and the LocalCommandLineCodeExecutor docstring claims a dangerous-command sanitizer that does not exist

Severity: high (composed default posture) and low (documentation defect), filed together because both concerns the same component and fix track. Default-posture security: peer content reaches host subprocesses with no approval and no model in the loop; the shipped docstring additionally tells developers a filter exists when it does not.

Affected versions: verified on autogen-agentchat 0.7.5 and autogen-ext 0.7.5 at commit 027ecf0a379bcc1d09956d46d12d44a3ad9cee14 (main head at time of report; repo in maintenance mode).

Part 1: the composed group-chat default

CodeExecutorAgent without a model_client is the shipped "terminal" participant for group chats:

DEFAULT_TERMINAL_DESCRIPTION = "A computer terminal that performs no other action than running Python scripts (provided to it quoted in ```python code blocks), or sh shell scripts (provided to it quoted in ```sh code blocks)."

(python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py:419)

When such an agent is a participant in any group chat, it extracts every markdown code block from incoming peer messages:

for msg in messages:
    if self._sources is None or msg.source in self._sources:
        if isinstance(msg, TextMessage):
            code_blocks.extend(self._extract_markdown_code_blocks(msg.content))

(python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py:679-683, block-extraction regex at :733-741)

and executes them after approval. The approval gate is off by default:

"The function can be either synchronous or asynchronous. If None (default), all code executions are automatically approved."

(python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py:142)

Paired with LocalCommandLineCodeExecutor (no container), the block runs as a subprocess on the host (python/packages/autogen-ext/src/autogen_ext/code_executors/local/init.py, the create_subprocess_exec path in _execute_code_dont_check_setup). Both layers emit UserWarnings at construction time (agent warning at _code_executor_agent.py:461-467, executor warning at local/init.py:163-168), but the default behavior is execute-without-asking.

Impact

In the default configuration (no approval_func, no sources allow-list, local executor), any participant's message content is executed on the host verbatim, deterministically, with no model in the loop. There is no authentication of "who wrote this code block" and no provenance: a peer agent that summarizes untrusted web content, or any third-party participant, is indistinguishable from the trusted coder agent the operator had in mind. The subprocess runs with the operator's privileges; the working directory confines where the executor writes the script file, not what the script can touch.

The shipped mitigations are real but opt-in: approval_func (per-block human or programmatic approval), sources (only execute blocks from named agents), and the Docker/ACA executors. All three must be supplied by the operator; the constructor default is the fully open combination. The documentation examples for CodeExecutorAgent consistently use DockerCommandLineCodeExecutor and show approval functions, but the shipped PythonCodeExecutionTool docstring example (python/packages/autogen-ext/src/autogen_ext/tools/code_execution/_code_execution.py:56) leads with LocalCommandLineCodeExecutor(work_dir="coding") and no approval, which is the posture reproduced here.

Part 2: the phantom sanitizer

The LocalCommandLineCodeExecutor class documentation states:

"Command line code is sanitized using regular expression match against a list of dangerous commands in order to prevent self-destructive commands from being executed which may potentially affect the users environment."

(python/packages/autogen-ext/src/autogen_ext/code_executors/local/init.py:57-58, class docstring)

No such sanitization exists anywhere in the execution path. The executor's _execute_code_dont_check_setup writes each code block to disk and runs it via create_subprocess_exec with only these content transformations before execution:

  • silence_pip(code, lang) (python/packages/autogen-ext/src/autogen_ext/code_executors/_common.py:114-131), which appends -qqq to pip install lines;
  • language normalization and filename extraction.

_common.py (the entire shared executor helper module, 199 lines) contains no list of dangerous commands and no rejection regex. A repository-wide search of autogen_ext/code_executors finds no other filter on the executed content.

Impact of Part 2

This is a false security claim in the shipped documentation of the component whose entire purpose is to run untrusted model-generated code on the host. A developer reading the docstring has a reasonable basis to believe destructive commands are filtered by the framework and to choose the local executor over a container on that basis; the belief is false, and the executor's own UserWarning (which recommends Docker) directly contradicts the same docstring. The two statements in one class documentation leave the reader with an incorrect model of the risk.

Reproduction (executed at commit 027ecf0a379bcc1d09956d46d12d44a3ad9cee14)

A two-agent RoundRobinGroupChat where an "untrusted peer" agent publishes a message containing an ```sh block; the second agent is a CodeExecutorAgent constructed with all defaults and a LocalCommandLineCodeExecutor. Both construction warnings fire. The executor agent:

  1. ran the peer's block, creating a marker file outside the executor work directory (RESULT_marker_outside_workdir = true);
  2. accepted a second block rm -rf <canary directory> and deleted the prepared directory (RESULT_canary_dir_deleted_by_rm_rf = true), confirming no command filtering occurs on the path.

Expected vs actual

Expected: the documented default for a component whose inputs come from other agents is either gated execution or no execution; and the docstring's sanitizer claim matches shipped behavior.

Actual: the default executes every participant's code blocks on the host without approval, and the documented sanitizer does not exist (the only content rewrite is the pip-quieting flag).

Recommended fix

For Part 1, any one of the following removes the dangerous default:

  • Require an explicit opt-in for automatic approval (for example approval_func=None with an auto_approve=True required, or defaulting approval_func to a console prompt like the docstring example already shows).
  • Default the group-chat sources=None behavior to "no sources" (execute nothing) rather than "all sources", with an explicit opt-in list.
  • Emit the construction warnings as loud, once-per-team diagnostics that name the participating agents whose content is executable.

For Part 2, either implement the described filter (a deny-list is of limited value; an allow-list or a dry-run parse step would be more honest) or, preferably, delete the sanitizer sentence and state explicitly that the executor performs no content filtering and that containment must come from the execution environment. A ready documentation-only patch is attached to this report (PR form): it replaces the sanitizer sentence with the honest statement and changes no runtime path.

Notes

  • A related design gap observed while auditing: CodeExecutorAgent._to_config cannot serialize approval_func (documented in code), so any declaratively loaded CodeExecutorAgent silently loses its approval gate.
  • Severity of Part 1 is conditioned on the operator pairing the defaults knowingly; the construction warnings are the project's good-faith mitigation and are disclosed here. The finding is that the composed default is dangerous specifically in the group-chat setting, where the content source is other agents rather than the operator's own task text.