Skip to content

Commit 17d3aef

Browse files
ekzhuclaude
andauthored
Add security warnings and default to DockerCommandLineCodeExecutor (#7035)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6f67b95 commit 17d3aef

7 files changed

Lines changed: 111 additions & 58 deletions

File tree

python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,18 @@ def __init__(
454454
self._approval_func = approval_func
455455
self._approval_func_is_async = approval_func is not None and iscoroutinefunction(approval_func)
456456

457+
# Issue warning if no approval function is set
458+
if approval_func is None:
459+
import warnings
460+
461+
warnings.warn(
462+
"No approval function set for CodeExecutorAgent. This means code will be executed automatically without human oversight. "
463+
"For security, consider setting an approval_func to review and approve code before execution. "
464+
"See the CodeExecutorAgent documentation for examples of approval functions.",
465+
UserWarning,
466+
stacklevel=2,
467+
)
468+
457469
if supported_languages is not None:
458470
self._supported_languages = supported_languages
459471
else:
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""Code executor utilities for AutoGen-Ext."""
2+
3+
import warnings
4+
from typing import Optional
5+
6+
from autogen_core.code_executor import CodeExecutor
7+
8+
# Docker imports for default code executor
9+
try:
10+
import docker as docker_client
11+
from docker.errors import DockerException
12+
13+
from .docker import DockerCommandLineCodeExecutor
14+
15+
_docker_available = True
16+
except ImportError:
17+
docker_client = None # type: ignore
18+
DockerException = Exception # type: ignore
19+
DockerCommandLineCodeExecutor = None # type: ignore
20+
_docker_available = False
21+
22+
from .local import LocalCommandLineCodeExecutor
23+
24+
25+
def _is_docker_available() -> bool:
26+
"""Check if Docker is available and running."""
27+
if not _docker_available:
28+
return False
29+
30+
try:
31+
if docker_client is not None:
32+
client = docker_client.from_env()
33+
client.ping() # type: ignore
34+
return True
35+
except DockerException:
36+
return False
37+
38+
return False
39+
40+
41+
def create_default_code_executor(work_dir: Optional[str] = None) -> CodeExecutor:
42+
"""Create a default code executor, preferring Docker if available.
43+
44+
This function creates a code executor using the following priority:
45+
1. DockerCommandLineCodeExecutor if Docker is available
46+
2. LocalCommandLineCodeExecutor with a warning if Docker is not available
47+
48+
Args:
49+
work_dir: Optional working directory for the code executor
50+
51+
Returns:
52+
CodeExecutor: A code executor instance
53+
54+
.. warning::
55+
For security, it is recommended to use DockerCommandLineCodeExecutor
56+
when available to isolate code execution.
57+
"""
58+
if _is_docker_available() and DockerCommandLineCodeExecutor is not None:
59+
try:
60+
if work_dir:
61+
return DockerCommandLineCodeExecutor(work_dir=work_dir)
62+
else:
63+
return DockerCommandLineCodeExecutor()
64+
except Exception:
65+
# Fallback to local if Docker fails to initialize
66+
pass
67+
68+
# Issue warning and use local executor if Docker is not available
69+
warnings.warn(
70+
"Docker is not available or not running. Using LocalCommandLineCodeExecutor instead of the recommended DockerCommandLineCodeExecutor. "
71+
"For security, it is recommended to install Docker and ensure it's running before using code executors. "
72+
"To install Docker, visit: https://docs.docker.com/get-docker/",
73+
UserWarning,
74+
stacklevel=2,
75+
)
76+
77+
if work_dir:
78+
return LocalCommandLineCodeExecutor(work_dir=work_dir)
79+
else:
80+
return LocalCommandLineCodeExecutor()
81+
82+
83+
__all__ = ["create_default_code_executor"]

python/packages/autogen-ext/src/autogen_ext/code_executors/docker/_docker_code_executor.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@
2323
FunctionWithRequirements,
2424
FunctionWithRequirementsStr,
2525
)
26+
from docker.types import DeviceRequest
2627
from pydantic import BaseModel
2728
from typing_extensions import Self
2829

29-
from docker.types import DeviceRequest
30-
3130
from .._common import (
3231
CommandLineCodeResult,
3332
build_python_functions_file,
@@ -43,7 +42,6 @@
4342

4443
try:
4544
import asyncio_atexit
46-
4745
import docker
4846
from docker.errors import DockerException, ImageNotFound, NotFound
4947
from docker.models.containers import Container

python/packages/autogen-ext/src/autogen_ext/code_executors/docker_jupyter/_docker_jupyter.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111

1212
from autogen_core import CancellationToken, Component
1313
from autogen_core.code_executor import CodeBlock, CodeExecutor, CodeResult
14-
from autogen_ext.code_executors._common import silence_pip
1514
from pydantic import BaseModel
1615
from typing_extensions import Self
1716

17+
from autogen_ext.code_executors._common import silence_pip
18+
1819
from ._jupyter_server import JupyterClient, JupyterConnectable, JupyterConnectionInfo, JupyterKernelClient
1920

2021

python/packages/autogen-ext/src/autogen_ext/code_executors/local/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,15 @@ def __init__(
159159
cleanup_temp_files: bool = True,
160160
virtual_env_context: Optional[SimpleNamespace] = None,
161161
):
162+
# Issue warning about using LocalCommandLineCodeExecutor
163+
warnings.warn(
164+
"Using LocalCommandLineCodeExecutor may execute code on the local machine which can be unsafe. "
165+
"For security, it is recommended to use DockerCommandLineCodeExecutor instead. "
166+
"To install Docker, visit: https://docs.docker.com/get-docker/",
167+
UserWarning,
168+
stacklevel=2,
169+
)
170+
162171
if timeout < 1:
163172
raise ValueError("Timeout must be greater than or equal to 1.")
164173
self._timeout = timeout

python/packages/autogen-ext/src/autogen_ext/teams/magentic_one.py

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -11,64 +11,14 @@
1111
from autogen_ext.agents.file_surfer import FileSurfer
1212
from autogen_ext.agents.magentic_one import MagenticOneCoderAgent
1313
from autogen_ext.agents.web_surfer import MultimodalWebSurfer
14-
from autogen_ext.code_executors.local import LocalCommandLineCodeExecutor
14+
from autogen_ext.code_executors import create_default_code_executor
1515
from autogen_ext.models.openai._openai_client import BaseOpenAIChatCompletionClient
1616

17-
# Docker imports for default code executor
18-
try:
19-
import docker
20-
from docker.errors import DockerException
21-
22-
from autogen_ext.code_executors.docker import DockerCommandLineCodeExecutor
23-
24-
_docker_available = True
25-
except ImportError:
26-
docker = None # type: ignore
27-
DockerException = Exception # type: ignore
28-
DockerCommandLineCodeExecutor = None # type: ignore
29-
_docker_available = False
30-
3117
SyncInputFunc = Callable[[str], str]
3218
AsyncInputFunc = Callable[[str, Optional[CancellationToken]], Awaitable[str]]
3319
InputFuncType = Union[SyncInputFunc, AsyncInputFunc]
3420

3521

36-
def _is_docker_available() -> bool:
37-
"""Check if Docker is available and running."""
38-
if not _docker_available:
39-
return False
40-
41-
try:
42-
if docker is not None:
43-
client = docker.from_env()
44-
client.ping() # type: ignore
45-
return True
46-
except DockerException:
47-
return False
48-
49-
return False
50-
51-
52-
def _create_default_code_executor() -> CodeExecutor:
53-
"""Create the default code executor, preferring Docker if available."""
54-
if _is_docker_available() and DockerCommandLineCodeExecutor is not None:
55-
try:
56-
return DockerCommandLineCodeExecutor()
57-
except Exception:
58-
# Fallback to local if Docker fails to initialize
59-
pass
60-
61-
# Issue warning and use local executor if Docker is not available
62-
warnings.warn(
63-
"Docker is not available or not running. Using LocalCommandLineCodeExecutor instead of the recommended DockerCommandLineCodeExecutor. "
64-
"For security, it is recommended to install Docker and ensure it's running before using MagenticOne. "
65-
"To install Docker, visit: https://docs.docker.com/get-docker/",
66-
UserWarning,
67-
stacklevel=3,
68-
)
69-
return LocalCommandLineCodeExecutor()
70-
71-
7222
class MagenticOne(MagenticOneGroupChat):
7323
"""
7424
MagenticOne is a specialized group chat class that integrates various agents
@@ -256,7 +206,7 @@ def __init__(
256206
DeprecationWarning,
257207
stacklevel=2,
258208
)
259-
code_executor = _create_default_code_executor()
209+
code_executor = create_default_code_executor()
260210

261211
fs = FileSurfer("FileSurfer", model_client=client)
262212
ws = MultimodalWebSurfer("WebSurfer", model_client=client)

python/packages/autogen-ext/tests/teams/test_magentic_one.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def test_docker_availability_check() -> None:
115115
assert isinstance(result, bool)
116116

117117

118-
@patch("autogen_ext.teams.magentic_one._is_docker_available")
118+
@patch("autogen_ext.code_executors._is_docker_available")
119119
def test_magentic_one_falls_back_to_local_when_docker_unavailable(
120120
mock_docker_check: Mock, mock_chat_client: Mock
121121
) -> None:
@@ -154,7 +154,7 @@ def test_magentic_one_falls_back_to_local_when_docker_unavailable(
154154
assert deprecated_warning_found, f"Deprecation warning not found in: {warning_messages}"
155155

156156

157-
@patch("autogen_ext.teams.magentic_one._is_docker_available")
157+
@patch("autogen_ext.code_executors._is_docker_available")
158158
def test_magentic_one_falls_back_to_local_with_approval_function(
159159
mock_docker_check: Mock, mock_chat_client: Mock
160160
) -> None:

0 commit comments

Comments
 (0)